Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mariomeyrelles/d0c59b6ea7c48354ccdd738a37647052 to your computer and use it in GitHub Desktop.
Save mariomeyrelles/d0c59b6ea7c48354ccdd738a37647052 to your computer and use it in GitHub Desktop.
Code samples from blog post: Deploy an Azure Function App connected to Cosmos DB with secrets on Key Vault using Bicep
@description('Application Name - change it!')
param appName string = 'sampleApp'
param location string = resourceGroup().location
param tenantId string = tenant().tenantId
@description('This is the object id of the user who will do the deployment on Azure. Can be your user id on AAD. Discover it running [az ad signed-in-user show] and get the [objectId] property.')
param deploymentOperatorId string
// a 4-char suffix to add to the various names of azure resources to help them be unique, but still, predictable
var appSuffix = substring(uniqueString(resourceGroup().id),0,4)
// grants access to the operator of this deployment with specific roles like key vault access.
module operatorSetup 'operatorSetup.bicep' = {
name: 'operatorSetup-deployment'
params: {
operatorPrincipalId: deploymentOperatorId
appName: appName
}
}
// creates an user-assigned managed identity that will used by different azure resources to access each other.
module msi 'msi.bicep' = {
name: 'msi-deployment'
params: {
location: location
managedIdentityName: '${appName}Identity'
operatorRoleDefinitionId: operatorSetup.outputs.roleId
}
}
// creates a key vault in this resource group
module keyvault 'keyvault.bicep' = {
name: 'keyvault-deployment'
params: {
location: location
appName: appName
tenantId: tenantId
}
}
// creates the cosmos db account and database with some containers configured. Saves connection string in keyvault.
module cosmos 'cosmosDb.bicep' = {
name: 'cosmos-deployment'
params:{
cosmosAccountId: '${appName}-${appSuffix}'
location: location
cosmosDbName: appName
keyVaultName: keyvault.outputs.keyVaultName
}
}
// creates a Log Analytics + Application Insights instance
module logAnalytics 'logAnalytics.bicep' = {
name: 'log-analytics-deployment'
params: {
appName: appName
}
}
// creates an azure function, with secrets stored in the key vault
module azureFunctions_api 'functionApp.bicep' = {
name: 'functions-app-deployment-api'
params: {
appName: appName
appInternalServiceName: 'api'
appNameSuffix: appSuffix
appInsightsInstrumentationKey: logAnalytics.outputs.instrumentationKey
keyVaultName: keyvault.outputs.keyVaultName
msiRbacId: msi.outputs.id
}
dependsOn: [
keyvault
logAnalytics
]
}
az ad signed-in-user show
resource roledefinition_deploymentOperator 'Microsoft.Authorization/roleDefinitions@2018-07-01' = {
name: guid('deployment-operator', appName)
properties: {
roleName: 'Operator role for app ${appName}'
description: 'This role orchestrates this deployment and allows the communication between the components in this solution.'
assignableScopes: [
resourceGroup().id
]
permissions: [
{
actions: [
'Microsoft.Authorization/*/read'
'Microsoft.Insights/alertRules/*'
'Microsoft.Resources/deployments/*'
'Microsoft.Resources/subscriptions/resourceGroups/read'
'Microsoft.Support/*'
'Microsoft.KeyVault/checkNameAvailability/read'
'Microsoft.KeyVault/deletedVaults/read'
'Microsoft.KeyVault/locations/*/read'
'Microsoft.KeyVault/vaults/*'
'Microsoft.KeyVault/operations/read'
]
dataActions:[
'Microsoft.KeyVault/vaults/*'
]
notActions: []
notDataActions: []
}
]
}
}
resource roledefinition_deploymentOperator 'Microsoft.Authorization/roleDefinitions@2018-07-01' = {
name: guid('deployment-operator', appName)
properties: {
roleName: 'Operator role for app ${appName}'
description: 'This role orchestrates this deployment and allows the communication between the components in this solution.'
assignableScopes: [
resourceGroup().id
]
permissions: [
{
actions: [
'Microsoft.Authorization/*/read'
'Microsoft.Insights/alertRules/*'
'Microsoft.Resources/deployments/*'
'Microsoft.Resources/subscriptions/resourceGroups/read'
'Microsoft.Support/*'
'Microsoft.KeyVault/checkNameAvailability/read'
'Microsoft.KeyVault/deletedVaults/read'
'Microsoft.KeyVault/locations/*/read'
'Microsoft.KeyVault/vaults/*'
'Microsoft.KeyVault/operations/read'
]
dataActions:[
'Microsoft.KeyVault/vaults/*'
]
notActions: []
notDataActions: []
}
]
}
}
// operatorSetup.bicep, called by main.bicep
@description('principalId of the user that will be given the permissions needed to operate this deployment.')
param operatorPrincipalId string
@description('Application name, used to compose the name of the role definitions.')
param appName string
var roleAssignmentName = guid(resourceGroup().id, roledefinition_deploymentOperator.id, operatorPrincipalId, appName)
// assigns the role definition above to the user who will perform the deployment.
resource keyvault_roleAssignment_deploymentOperator 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = {
name: roleAssignmentName
scope: resourceGroup()
properties: {
roleDefinitionId: roledefinition_deploymentOperator.id // remember that this the user who is doing the deployment!
principalId: operatorPrincipalId
}
}
output roleId string = roledefinition_deploymentOperator.id
output roleName string = roledefinition_deploymentOperator.name // just for troubleshooting if you need it
output roleType string = roledefinition_deploymentOperator.type // just for troubleshooting if you need it
az deployment group create --resource-group sample-app-blog-post-eastus2 --template-file .\src\main.bicep --parameters deploymentOperatorId=aaaabbbb-ccdd-ee11-2233-444455667788
// keyvault.bicep, called by main.bicep
param appName string
@maxLength(24)
param vaultName string = '${'kv-'}${appName}-${substring(uniqueString(resourceGroup().id), 0, 23 - (length(appName) + 3))}' // must be globally unique
param location string = resourceGroup().location
param sku string = 'Standard'
param tenantId string // replace with your tenantId
param enabledForDeployment bool = true
param enabledForTemplateDeployment bool = true
param enabledForDiskEncryption bool = true
param enableRbacAuthorization bool = true
param softDeleteRetentionInDays int = 90
param networkAcls object = {
ipRules: []
virtualNetworkRules: []
}
resource keyvault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = {
name: vaultName
location: location
properties: {
tenantId: tenantId
sku: {
family: 'A'
name: sku
}
enabledForDeployment: enabledForDeployment
enabledForDiskEncryption: enabledForDiskEncryption
enabledForTemplateDeployment: enabledForTemplateDeployment
softDeleteRetentionInDays: softDeleteRetentionInDays
enableRbacAuthorization: enableRbacAuthorization
networkAcls: networkAcls
// note: no need for access policies here!
}
}
output keyVaultName string = keyvault.name
output keyVaultId string = keyvault.id
@maxLength(30)
param cosmosAccountId string
param location string
param cosmosDbName string
param keyVaultName string
param tags object = {
'deploymentGroup': 'cosmosdb'
}
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2021-10-15' = {
name: toLower(cosmosAccountId)
location: location
kind: 'GlobalDocumentDB'
properties: {
publicNetworkAccess: 'Enabled'
enableFreeTier: false
databaseAccountOfferType: 'Standard'
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
maxIntervalInSeconds: 5
maxStalenessPrefix: 100
}
locations: [
{
locationName: location
failoverPriority: 0
isZoneRedundant: false
}
]
capabilities: [
{
name: 'EnableServerless'
}
]
}
}
resource cosmosDb_database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-06-15' = {
name: '${cosmosAccount.name}/${cosmosDbName}'
tags: tags
properties: {
resource: {
id: cosmosDbName
}
}
}
resource container_leases 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2021-06-15' = {
name: '${cosmosDb_database.name}/leases'
tags: tags
dependsOn: [
cosmosAccount
]
properties: {
resource: {
id: 'leases'
partitionKey: {
paths: [
'/id'
]
}
}
}
}
resource container_employees 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2021-06-15' = {
name: '${cosmosDb_database.name}/Employees'
tags: tags
dependsOn: [
cosmosAccount
]
properties: {
resource: {
id: 'Employees'
partitionKey: {
paths: [
'/EmployeeId'
]
}
uniqueKeyPolicy: {
uniqueKeys: [
{
paths: [
'/EmployeeId'
]
}
]
}
}
}
}
module setCosmosConnectionString 'setSecret.bicep' = {
name: 'setCosmosConnectionString'
params: {
keyVaultName: keyVaultName
secretName: 'CosmosDbConnectionString'
secretValue: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString
}
}
output cosmosAccountId string = cosmosAccountId
param keyVaultName string
param secretName string
param secretValue string
resource secret 'Microsoft.KeyVault/vaults/secrets@2021-06-01-preview' = {
name: '${keyVaultName}/${secretName}'
properties: {
value: secretValue
}
}
// logAnalytics.bicep, called by main.bicep
param appName string
param location string = resourceGroup().location
var logAnalyticsWorkspaceName = 'log-${appName}'
var appInsightsName = 'appi-${appName}'
// Log Analytics workspace is required for new Application Insights deployments
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
location: location
name: logAnalyticsWorkspaceName
properties: {
retentionInDays: 30
features: {
enableLogAccessUsingOnlyResourcePermissions: true
}
sku: {
name: 'PerGB2018'
}
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
}
// App Insights resource
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: appInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalyticsWorkspace.id
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
}
output instrumentationKey string = appInsights.properties.InstrumentationKey
// functionApp.bicep, called by main.bicep
// note: https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations
param location string = resourceGroup().location
param functionRuntime string = 'dotnet'
@description('A name for this whole project, used to help name individual resources')
param appName string
@description('The name of the role or service of this function. Example: Api CommandHandler, EventHandler')
param appInternalServiceName string
@description('Id of a existing keyvault that will be used to store and retrieve keys in this deployment')
param keyVaultName string
@description('User-assigned managed identity that will be attached to this function and will have power to connect to different resources.')
param msiRbacId string
@description('Application insights instrumentation key.')
param appInsightsInstrumentationKey string
param deploymentDate string = utcNow()
param appNameSuffix string
var functionAppName = 'func-${appName}-${appInternalServiceName}-${appNameSuffix}'
var appServiceName = 'ASP-${appName}${appInternalServiceName}-${appNameSuffix}'
// remove dashes for storage account name
var storageAccountName = toLower(format('st{0}', replace('${appInternalServiceName}-${appNameSuffix}', '-', '')))
var appTags = {
AppID: '${appName}-${appInternalServiceName}'
AppName: '${appName}-${appInternalServiceName}'
}
// Storage Account - I am using 1 storage account for each function. It would be potentially shared across many function apps.
resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
tags: appTags
}
// Blob Services for Storage Account
resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2019-06-01' = {
parent: storageAccount
name: 'default'
properties: {
cors: {
corsRules: []
}
deleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultName
scope: resourceGroup()
}
module setStorageAccountSecret 'setSecret.bicep' = {
name: 'stgSecret-${appInternalServiceName}-${deploymentDate}'
params: {
keyVaultName: keyVault.name
secretName: '${storageAccount.name}-${appInternalServiceName}-ConnectionString'
secretValue: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
}
// App Service
resource appService 'Microsoft.Web/serverfarms@2020-12-01' = {
name: appServiceName
location: location
kind: 'functionapp'
sku: {
name: 'Y1'
tier: 'Dynamic'
size: 'Y1'
family: 'Y'
capacity: 0
}
properties: {
maximumElasticWorkerCount: 1
targetWorkerCount: 0
targetWorkerSizeId: 0
}
tags: appTags
}
// Function App
resource functionApp 'Microsoft.Web/sites@2020-12-01' = {
name: functionAppName
location: location
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: {
'${msiRbacId}': {}
}
}
kind: 'functionapp'
properties: {
keyVaultReferenceIdentity: msiRbacId
enabled: true
hostNameSslStates: [
{
name: '${functionAppName}.azurewebsites.net'
sslState: 'Disabled'
hostType: 'Standard'
}
{
name: '${functionAppName}.scm.azurewebsites.net'
sslState: 'Disabled'
hostType: 'Standard'
}
]
serverFarmId: appService.id
siteConfig: {
appSettings: [
{
name: 'AzureWebJobsStorage'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=${storageAccount.name}-${appInternalServiceName}-ConnectionString)'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=${storageAccount.name}-${appInternalServiceName}-ConnectionString)'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsightsInstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: 'InstrumentationKey=${appInsightsInstrumentationKey}'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: functionRuntime
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~3'
}
{
name: 'CosmosDbConnectionString'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=CosmosDbConnectionString)'
}
]
use32BitWorkerProcess: true
}
scmSiteAlsoStopped: false
clientAffinityEnabled: false
clientCertEnabled: false
hostNamesDisabled: false
dailyMemoryTimeQuota: 0
httpsOnly: false
redundancyMode: 'None'
}
tags: appTags
}
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: {
'${msiRbacId}': {}
}
}
'@Microsoft.KeyVault(VaultName=kv-some-keyVault;SecretName=CosmosDbConnectionString)'
az keyvault purge -n kv-sampleApp-1234 --no-wait
az ad signed-in-user show
az role definition list > roleDefs.out
az role assignment list > roleAssignments.out
az role assignment delete --id "/subscriptions/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleAssignments/<object id of the identity>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment