From b0377e3282998e06462311a87b2956d084ab2d19 Mon Sep 17 00:00:00 2001 From: Cephas Lin Date: Fri, 6 Sep 2024 10:15:54 +0000 Subject: [PATCH] convert to key vault secrets --- README.md | 6 +++++ infra/main.bicep | 5 ++++ infra/resources.bicep | 53 +++++++++++++++++++++++++++++++------------ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f97c89261..38eb4df4d 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ Because the Linux .NET container in App Service doesn't come with the .NET SDK, - In the [.csproj](DotNretCoreSqlDb.csproj) file, include the generated *migrationsbundle* file. During the `azd package` stage, *migrationsbundle* will be added to the deploy package. - In [infra/resources.bicep](infra/resources.bicep), add the `appCommandLine` property to the web app to run the uploaded *migrationsbundle*. +## How does the AZD template configure passwords? + +Two types of secrets are involved: the SQL Database administrator password and the access key for Cache for Redis, and they are both present in the respective connection strings. The [AZD template](infra/resources.bicep) in this repo manages both connection strings in a key vault that's secured behind a private endpoint. + +To simplify the scenario, the AZD template generates a new database password each time you run `azd provision` or `azd up`, and the database connection string in the key vault is modified too. If you want to fully utilize `secretOrRandomPassword` in the [parameter file](infra/main.parameters.json) by committing the automatically generated password to the key vault the first time and reading it on subsequent `azd` commands, you must relax the networking restriction of the key vault to allow traffic from public networks. For more information, see [What is the behavior of the `secretOrRandomPassword` function?](https://learn.microsoft.com/azure/developer/azure-developer-cli/faq#what-is-the-behavior-of-the--secretorrandompassword--function). + ## Getting help If you're working with this project and running into issues, please post in [Issues](/issues). diff --git a/infra/main.bicep b/infra/main.bicep index bf40db881..7c0d28323 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -9,6 +9,10 @@ param name string @description('Primary location for all resources') param location string +@secure() +@description('SQL Server administrator password') +param databasePassword string = '' + param principalId string = '' var resourceToken = toLower(uniqueString(subscription().id, name, location)) @@ -26,6 +30,7 @@ module resources 'resources.bicep' = { name: name location: location resourceToken: resourceToken + databasePassword: databasePassword principalId: principalId } } diff --git a/infra/resources.bicep b/infra/resources.bicep index f6dae6f79..e7f1ad6b4 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -2,6 +2,8 @@ param name string param location string param resourceToken string param principalId string +@secure() +param databasePassword string var appName = '${name}-${resourceToken}' @@ -211,7 +213,8 @@ resource privateDnsZoneCache 'Microsoft.Network/privateDnsZones@2020-06-01' = { } } -// The Key Vault is used to manage Redis secrets. +// The Key Vault is used to manage SQL database and redis secrets. +// Current user has the admin permissions to configure key vault secrets, but by default doesn't have the permissions to read them. resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { name: '${take(replace(appName, '-', ''), 17)}-vault' location: location @@ -219,26 +222,38 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { enableRbacAuthorization: true tenantId: subscription().tenantId sku: { family: 'A', name: 'standard' } - publicNetworkAccess: 'Disabled' + // Only allow requests from the private endpoint in the VNET. + publicNetworkAccess: 'Disabled' // To see the secret in the portal, change to 'Enabled' networkAcls: { - defaultAction: 'Deny' - bypass: 'AzureServices' - ipRules: [] - virtualNetworkRules: [] + defaultAction: 'Deny' // To see the secret in the portal, change to 'Allow' + bypass: 'None' } } } +// Grant the current user with key vault secret user role permissions over the key vault. This lets you inspect the secrets, such as in the portal +// If you remove this section, you can't read the key vault secrets, but the app still has access with its managed identity. +resource keyVaultSecretUserRoleRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { + scope: subscription() + name: '4633458b-17de-408a-b874-0445c86b69e6' // The built-in Key Vault Secret User role +} +resource keyVaultSecretUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { + scope: keyVault + name: guid(resourceGroup().id, principalId, keyVaultSecretUserRoleRoleDefinition.id) + properties: { + roleDefinitionId: keyVaultSecretUserRoleRoleDefinition.id + principalId: principalId + principalType: 'User' + } +} + // The SQL Database server is configured to be the minimum pricing tier -// It also uses Microsoft Entra authentication with the current user as the administrator resource dbserver 'Microsoft.Sql/servers@2023-05-01-preview' = { location: location name: '${appName}-server' properties: { - administrators: { - login: '${appName}-server-admin' - sid: principalId - } + administratorLogin: '${appName}-server-admin' + administratorLoginPassword: databasePassword publicNetworkAccess: 'Disabled' restrictOutboundNetworkAccess: 'Disabled' } @@ -319,7 +334,7 @@ resource web 'Microsoft.Web/sites@2022-09-01' = { properties: { applicationLogs: { fileSystem: { - level: 'Verbose' + level: 'Information' } } detailedErrorMessages: { @@ -356,7 +371,7 @@ resource vaultConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { scope: web name: 'vaultConnector' properties: { - clientType: 'springBoot' + clientType: 'dotnet' targetService: { type: 'AzureResource' id: keyVault.id @@ -383,7 +398,15 @@ resource dbConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { id: dbserver::db.id } authInfo: { - authType: 'systemAssignedIdentity' + authType: 'secret' + name: '${appName}-server-admin' + secretInfo: { + secretType: 'rawValue' + value: databasePassword + } + } + secretStore: { + keyVaultId: keyVault.id // Configure secrets as key vault references. No secret is exposed in App Service. } clientType: 'dotnet-connectionString' // Generate a .NET connection string. For app setting, use 'dotnet' instead vNetSolution: { @@ -403,7 +426,7 @@ resource cacheConnector 'Microsoft.ServiceLinker/linkers@2024-04-01' = { id: resourceId('Microsoft.Cache/Redis/Databases', redisCache.name, '0') } authInfo: { - authType: 'accessKey' // Configure secrets as Key Vault references. No secret is exposed in App Service. + authType: 'accessKey' // Configure secrets as key vault references. No secret is exposed in App Service. } secretStore: { keyVaultId: keyVault.id