-
Notifications
You must be signed in to change notification settings - Fork 189
Access to high security leveled Azure Storage in Teams Apps
First of all, all those changes are need running as the user that granted to be Contributor, Owner or Role Based Access Control Administrator. Otherwise, the Microsoft.Authorization/roleAssignments
part in the Bicep will running fail.
resource storage 'Microsoft.Storage/storageAccounts@2021-06-01' = {
name: storageName
kind: 'StorageV2'
location: location
sku: {
name: storageSKU
}
properties: {
allowSharedKeyAccess: false // this line makes the SAS disabled.
}
}
Use you bot's identity to authorized between Azure Function App/App Service and Azure Storage.
// The managed identity that use to connect between bot and Azure Function
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
location: location
name: identityName
}
// Grant Blob Data Contributor role to this managed identity
// https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var StorageBlobDataContributorRole = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('${storageName}-role-assignment')
scope: storage
properties: {
principalId: identity.properties.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', StorageBlobDataContributorRole)
}
}
resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
kind: 'functionapp'
location: location
name: functionAppName
properties: {
serverFarmId: serverfarm.id
httpsOnly: true
siteConfig: {
alwaysOn: true
appSettings: [
// add the storage's name to the environment variable
{
name: 'STORAGE_NAME'
value: storageName
}
// managed identity id should be also add to the environment varibale
{
name: 'MANAGED_IDENTITY_ID'
value: identity.properties.clientId
}
]
ftpsState: 'FtpsOnly'
}
}
// function app also need this managed identity to connect with bot
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${identity.id}': {}
}
}
}
Code change is based on this docs
Note: this code cannot be run in a local debugging environment. You may need to keep the old logic to make sure it can be run on local.
const credential = new DefaultAzureCredential({ managedIdentityClientId: process.env["MANAGED_IDENTITY_ID"] });
const blobServiceClient = new BlobServiceClient(`https://${process.env["STORAGE_NAME"]}.blob.core.windows.net`, credential);
const containerClient = blobServiceClient.getContainerClient("YOUR_BLOB_CONTAINER_NAME");
// test read
const contentBuffer = await containerClient.getBlockBlobClient("YOUR_FILE_NAME").downloadToBuffer();
console.log(content.toString());
// test write
const test = Buffer.from("TEST CONTENT");
const write = containerClient.getBlockBlobClient("TEST_WRITE_FILE_NAME");
await write.upload(test, test.length);
Use managed identity to connect between Function/App Service and Azure Storage
resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
kind: 'functionapp'
location: location
name: functionAppName
properties: {
serverFarmId: serverfarm.id
httpsOnly: true
siteConfig: {
alwaysOn: true
appSettings: [
// add the storage's name to the environment variable
{
name: 'STORAGE_NAME'
value: storageName
}
]
ftpsState: 'FtpsOnly'
}
}
// use the system assigned identity in function or app service
identity: {
type: 'SystemAssigned'
}
}
resource storage 'Microsoft.Storage/storageAccounts@2021-06-01' = {
name: storageName
kind: 'StorageV2'
location: location
sku: {
name: storageSKU
}
properties: {
// disable SAS
allowSharedKeyAccess: false
}
}
// Grant Blob Data Contributor role to this managed identity
// https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
var StorageBlobDataContributorRole = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('${storageName}-role-assignment')
scope: storage
properties: {
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', StorageBlobDataContributorRole)
}
}
In this solution, we use system assigned identity, so we just use the DefaultAzureCredential
to get our identity.
const credential = new DefaultAzureCredential();
const blobServiceClient = new BlobServiceClient(`https://${process.env["STORAGE_NAME"]}.blob.core.windows.net`, credential);
const containerClient = blobServiceClient.getContainerClient("YOUR_BLOB_CONTAINER_NAME");
// test read
const contentBuffer = await containerClient.getBlockBlobClient("YOUR_FILE_NAME").downloadToBuffer();
console.log(content.toString());
// test write
const test = Buffer.from("TEST CONTENT");
const write = containerClient.getBlockBlobClient("TEST_WRITE_FILE_NAME");
await write.upload(test, test.length);
When public access disabled, we need create a Vnet to connect between Azure Function/App Service to Azure Storage. This change will only be implemented by the Bicep.
// Define a Network Security Group
resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
name: '${resourceBaseName}-NSG'
location: location
properties: {
securityRules: []
}
}
// Define a Virtual Network
resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
name: '${resourceBaseName}-VNet'
location: location
properties: {
addressSpace: {
addressPrefixes: ['10.0.0.0/16']
}
subnets: [
{
name: 'default'
properties: {
addressPrefix: '10.0.10.0/24'
networkSecurityGroup: {
id: networkSecurityGroup.id
}
serviceEndpoints: [
{
service: 'Microsoft.Storage'
locations: ['*']
}
]
delegations: [
{
name: 'delegation'
properties: {
serviceName: 'Microsoft.Web/serverfarms'
}
type: 'Microsoft.Network/virtualNetworks/subnets/delegations'
}
]
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Disabled'
}
}
]
}
}
resource storage 'Microsoft.Storage/storageAccounts@2021-06-01' = {
name: storageName
kind: 'StorageV2'
location: location
sku: {
name: storageSKU
}
properties: {
allowBlobPublicAccess: false
networkAcls: {
bypass: 'AzureServices'
// deny public access
defaultAction: 'Deny'
// add to the vnet
virtualNetworkRules: [
{
id: vnet.properties.subnets[0].id
action: 'Allow'
}
]
}
}
}
resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
kind: 'functionapp'
location: location
name: functionAppName
properties: {
serverFarmId: serverfarm.id
httpsOnly: true
// just add this line to add function to the vnet
virtualNetworkSubnetId: vnet.properties.subnets[0].id
}
}
Build Custom Engine Copilots
- Build a basic AI chatbot for Teams
- Build an AI agent chatbot for Teams
- Expand AI bot's knowledge with your content
Scenario-based Tutorials
- Send notifications to Teams
- Respond to chat commands in Teams
- Respond to card actions in Teams
- Embed a dashboard canvas in Teams
Extend your app across Microsoft 365
- Teams tabs in Microsoft 365 and Outlook
- Teams message extension for Outlook
- Add Outlook Add-in to a Teams app
App settings and Microsoft Entra Apps
- Manage Application settings with Teams Toolkit
- Manage Microsoft Entra Application Registration with Teams Toolkit
- Use an existing Microsoft Entra app
- Use a multi-tenant Microsoft Entra app
Configure multiple capabilities
- How to configure Tab capability within your Teams app
- How to configure Bot capability within your Teams app
- How to configure Message Extension capability within your Teams app
Add Authentication to your app
- How to add single sign on in Teams Toolkit for Visual Studio Code
- How to enable Single Sign-on in Teams Toolkit for Visual Studio
Connect to cloud resources
- How to integrate Azure Functions with your Teams app
- How to integrate Azure API Management
- Integrate with Azure SQL Database
- Integrate with Azure Key Vault
Deploy apps to production