-
Notifications
You must be signed in to change notification settings - Fork 243
Access to high security leveled Azure Storage in Teams Apps
FanH edited this page Aug 14, 2024
·
6 revisions
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('resourceBaseName-${uniqueString('StorageRoleAssignment')}')
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
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('resourceBaseName-${uniqueString('StorageRoleAssignment')}')
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