Skip to content

leverage ServiceConnector to setup connection to keyvault and DB - Keyvault reference #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions infra/app/connection.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
param authType string
//param appResourceId string
param targetResourceId string
param runtimeName string
param dbUserName string = ''
param keyVaultName string
param webAppName string
param connectionStringKey string = ''

@secure()
param dbUserPassword string

var resourcePrefix = uniqueString(webAppName)

module connections '../core/host/servicelinker.bicep' = {
name: '${resourcePrefix}conns'
params: {
authType: authType
//appResourceId: appResourceId
targetResourceId: targetResourceId
runtimeName: runtimeName
dbUserName: dbUserName
dbUserPassword: dbUserPassword
keyVaultName: keyVaultName
webAppName: webAppName
connectionStringKey: connectionStringKey
}
}
1 change: 1 addition & 0 deletions infra/app/db.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ module sqlServer '../core/database/sqlserver/sqlserver.bicep' = {

output connectionStringKey string = sqlServer.outputs.connectionStringKey
output databaseName string = sqlServer.outputs.databaseName
output id string = sqlServer.outputs.id
10 changes: 1 addition & 9 deletions infra/core/database/sqlserver/sqlserver.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,10 @@ resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' =
}
}

resource sqlAzureConnectionStringSercret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
parent: keyVault
name: connectionStringKey
properties: {
value: '${connectionString}; Password=${appUserPassword}'
}
}

resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: keyVaultName
}

var connectionString = 'Server=${sqlServer.properties.fullyQualifiedDomainName}; Database=${sqlServer::database.name}; User=${appUser}'
output connectionStringKey string = connectionStringKey
output databaseName string = sqlServer::database.name
output id string = sqlServer.id
27 changes: 22 additions & 5 deletions infra/core/host/appservice.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ param numberOfWorkers int = -1
param scmDoBuildDuringDeployment bool = false
param use32BitWorkerProcess bool = false


resource appService 'Microsoft.Web/sites@2022-03-01' = {
name: name
location: location
Expand Down Expand Up @@ -66,8 +67,7 @@ resource appService 'Microsoft.Web/sites@2022-03-01' = {
SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment)
ENABLE_ORYX_BUILD: string(enableOryxBuild)
},
!empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {},
!empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {})
!empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {})
}

resource configLogs 'config' = {
Expand All @@ -84,14 +84,31 @@ resource appService 'Microsoft.Web/sites@2022-03-01' = {
}
}

resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) {
name: keyVaultName
}
// resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) {
// name: keyVaultName
// }


resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) {
name: applicationInsightsName
}

// resource appUserPasswordSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
// parent: keyVault
// name: 'appServiceUserPassword'
// properties: {
// value: appUserPassword
// }
// }

//resource appUserdSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
// parent: keyVault
// name: 'appServiceUser'
// properties: {
// value: dbUserName
// }
//}

output identityPrincipalId string = managedIdentity ? appService.identity.principalId : ''
output name string = appService.name
output uri string = 'https://${appService.properties.defaultHostName}'
80 changes: 80 additions & 0 deletions infra/core/host/servicelinker.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
param authType string
//param appResourceId string
param targetResourceId string
param runtimeName string
param dbUserName string = ''
param keyVaultName string
param webAppName string
param connectionStringKey string =''

@secure()
param dbUserPassword string = ''


// if !empty(keyVaultName), create connection to keyvault so that db credentials could be saved into keyvault,
// and app service could retrieve secrets from keyvault using managed identity
resource connectionToKeyVault 'Microsoft.ServiceLinker/linkers@2022-11-01-preview' = if(!empty(keyVaultName)) {
name: 'conn_kv'
scope: webApp
properties: {
targetService: {
id: keyVault.id
type: 'AzureResource'
}
clientType: 'none'
authInfo: {
authType: 'systemAssignedIdentity'
roles: [
'4633458b-17de-408a-b874-0445c86b69e6'
]
}
configurationInfo: {
customizedKeys: {
'AZURE_KEYVAULT_RESOURCEENDPOINT': 'AZURE_KEY_VAULT_ENDPOINT'
}
}
}
dependsOn: [
webApp
]
}

// if !empty(targetResourceId), create connection to target database, including:
// - add db connectionstr (from keyvault if applicable) in webapp appsettings or connectionString(for dotnetcore convention)
// - allow webapp firewall at target database if applicable (target allows firewall instead of public access)
resource connectionToTargetDB 'Microsoft.ServiceLinker/linkers@2022-11-01-preview' = if (!empty(targetResourceId)) {
name: 'conn_db'
scope: webApp
properties: {
targetService: {
id: targetResourceId
type: 'AzureResource'
}
secretStore: {
keyVaultId: !empty(keyVaultName) ? keyVault.id : ''
keyVaultSecretName: !empty(keyVaultName) ? connectionStringKey : ''
}
authInfo: {
authType: authType
name: dbUserName
secretInfo: {
secretType: 'rawValue'
value: dbUserPassword
}
}
clientType: runtimeName
}
dependsOn: [
connectionToKeyVault
]
}

resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!empty(keyVaultName)) {
name: keyVaultName
scope: resourceGroup()
}

resource webApp 'Microsoft.Web/sites@2022-03-01' existing = {
name: webAppName
scope: resourceGroup()
}
41 changes: 28 additions & 13 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ param sqlServerName string = ''
param sqlDatabaseName string = ''
param webServiceName string = ''
param apimServiceName string = ''
param appUser string = 'appUser'

@description('Flag to use Azure API Management to mediate the calls between the Web frontend and the backend API')
param useAPIM bool = false
Expand Down Expand Up @@ -75,19 +76,6 @@ module api './app/api.bicep' = {
appServicePlanId: appServicePlan.outputs.id
keyVaultName: keyVault.outputs.name
allowedOrigins: [ web.outputs.SERVICE_WEB_URI ]
appSettings: {
AZURE_SQL_CONNECTION_STRING_KEY: sqlServer.outputs.connectionStringKey
}
}
}

// Give the API access to KeyVault
module apiKeyVaultAccess './core/security/keyvault-access.bicep' = {
name: 'api-keyvault-access'
scope: rg
params: {
keyVaultName: keyVault.outputs.name
principalId: api.outputs.SERVICE_API_IDENTITY_PRINCIPAL_ID
}
}

Expand Down Expand Up @@ -132,6 +120,33 @@ module keyVault './core/security/keyvault.bicep' = {
}
}

// creation connections
// 1. if !empty(keyVaultName), create connection to keyvault so that db credentials could be saved into keyvault,
// and app service could retrieve secrets from keyvault
// 2. create connection to target database
module connections './app/connection.bicep' = {
name: 'conn${resourceToken}'
scope: rg
//scope: apiWebApp
//scope: api.outputs.SERVICE_API_NAME
params: {
authType: 'secret'
//appResourceId: api.outputs.api.id
targetResourceId: '${sqlServer.outputs.id}/databases/${sqlServer.outputs.databaseName}'
runtimeName: 'dotnet'
dbUserName: appUser
dbUserPassword: appUserPassword
keyVaultName: keyVault.outputs.name
webAppName: api.outputs.SERVICE_API_NAME
connectionStringKey: 'AZURE-SQL-CONNECTION-STRING'
}
}

// resource apiWebApp 'Microsoft.Web/sites@2022-03-01' existing = {
// name: api.outputs.SERVICE_API_NAME
// scope: rg
// }

// Monitor application with Azure Monitor
module monitoring './core/monitor/monitoring.bicep' = {
name: 'monitoring'
Expand Down
12 changes: 9 additions & 3 deletions src/api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
using SimpleTodo.Api;

var builder = WebApplication.CreateBuilder(args);
var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential());
builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential);
// if non-development node, get target connection string from runtime directly, because Azure webapp resolve keyvault reference already.
var connectionString = builder.Configuration["AZURE_SQL_CONNECTIONSTRING"];
// if development node, get target connection string from keyvault config provider.
if (builder.Environment.IsDevelopment())
{
var credential = new ChainedTokenCredential(new AzureDeveloperCliCredential(), new DefaultAzureCredential());
builder.Configuration.AddAzureKeyVault(new Uri(builder.Configuration["AZURE_KEY_VAULT_ENDPOINT"]), credential);
connectionString = builder.Configuration["AZURE-SQL-CONNECTION-STRING"];
}

builder.Services.AddScoped<ListsRepository>();
builder.Services.AddDbContext<TodoDb>(options =>
{
var connectionString = builder.Configuration[builder.Configuration["AZURE_SQL_CONNECTION_STRING_KEY"]];
options.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure());
});

Expand Down