From 1b2c0133477bb454edcec4158eb1de1c7f2b8de7 Mon Sep 17 00:00:00 2001 From: Are Almaas Date: Wed, 6 Mar 2024 09:58:28 +0100 Subject: [PATCH] feat(azure): add redis resource (#518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to https://github.com/digdir/dialogporten/issues/275 - Added Redis resource to infrastructure - Added `host name` to the key vault and app configuration to make it accessible to the container app. - Using managed identity to connect the web apis to Redis ~~Doesn't seem like managed identity is possible for apps that read/write to the redis instance. We could use a pre-defined role, but that would only enable control plane level permissions.. ¯\_(ツ)_/¯~~ --- .azure/applications/web-api-eu/main.bicep | 26 +++++++++++ .../web-api-eu/staging.bicepparam | 1 + .../applications/web-api-eu/test.bicepparam | 1 + .../web-api-migration-job/staging.bicepparam | 1 + .azure/applications/web-api-so/main.bicep | 26 +++++++++++ .../web-api-so/staging.bicepparam | 1 + .../applications/web-api-so/test.bicepparam | 1 + .azure/infrastructure/main.bicep | 44 ++++++++++++++---- .azure/infrastructure/production.bicepparam | 8 ++++ .azure/infrastructure/soak.bicepparam | 8 ++++ .azure/infrastructure/staging.bicepparam | 8 ++++ .azure/infrastructure/test.bicepparam | 8 ++++ .azure/modules/postgreSql/create.bicep | 6 +-- .azure/modules/redis/main.bicep | 45 +++++++++++++++++++ .github/workflows/action-deploy-apps.yml | 3 ++ .github/workflows/ci-cd-main.yml | 1 + .github/workflows/ci-cd-staging.yml | 1 + 17 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 .azure/modules/redis/main.bicep diff --git a/.azure/applications/web-api-eu/main.bicep b/.azure/applications/web-api-eu/main.bicep index aca633672..6d2962964 100644 --- a/.azure/applications/web-api-eu/main.bicep +++ b/.azure/applications/web-api-eu/main.bicep @@ -14,6 +14,9 @@ param apimIp string param containerAppEnvironmentName string @minLength(3) @secure() +param redisName string +@minLength(3) +@secure() param appInsightConnectionString string @minLength(5) @secure() @@ -33,6 +36,10 @@ resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' name: containerAppEnvironmentName } +resource redis 'Microsoft.Cache/redis@2023-08-01' existing = { + name: redisName +} + var containerAppEnvVars = [ { name: 'ASPNETCORE_ENVIRONMENT' @@ -70,6 +77,25 @@ module containerApp '../../modules/containerApp/main.bicep' = { } } +resource redisCustomAccessPolicy 'Microsoft.Cache/redis/accessPolicies@2023-08-01' = { + parent: redis + name: containerAppName + properties: { + permissions: 'Contributor' + } +} + +resource redisCustomAccessPolicyAssignment 'Microsoft.Cache/redis/accessPolicyAssignments@2023-08-01' = { + parent: redis + name: containerAppName + properties: { + accessPolicyName: containerAppName + objectId: containerApp.outputs.identityPrincipalId + objectIdAlias: '${containerAppName}-access-policy-redis' + } + dependsOn: [redisCustomAccessPolicy] +} + module keyVaultReaderAccessPolicy '../../modules/keyvault/addReaderRoles.bicep' = { name: 'keyVaultReaderAccessPolicy-${containerAppName}' params: { diff --git a/.azure/applications/web-api-eu/staging.bicepparam b/.azure/applications/web-api-eu/staging.bicepparam index 447dcb2be..5b4a8010a 100644 --- a/.azure/applications/web-api-eu/staging.bicepparam +++ b/.azure/applications/web-api-eu/staging.bicepparam @@ -10,3 +10,4 @@ param environmentKeyVaultName = readEnvironmentVariable('ENVIRONMENT_KEY_VAULT_N param containerAppEnvironmentName = readEnvironmentVariable('CONTAINER_APP_ENVIRONMENT_NAME') param appInsightConnectionString = readEnvironmentVariable('APP_INSIGHTS_CONNECTION_STRING') param appConfigurationName = readEnvironmentVariable('APP_CONFIGURATION_NAME') +param redisName = readEnvironmentVariable('REDIS_NAME') diff --git a/.azure/applications/web-api-eu/test.bicepparam b/.azure/applications/web-api-eu/test.bicepparam index 9df5c026b..e40c26f7f 100644 --- a/.azure/applications/web-api-eu/test.bicepparam +++ b/.azure/applications/web-api-eu/test.bicepparam @@ -10,3 +10,4 @@ param environmentKeyVaultName = readEnvironmentVariable('ENVIRONMENT_KEY_VAULT_N param containerAppEnvironmentName = readEnvironmentVariable('CONTAINER_APP_ENVIRONMENT_NAME') param appInsightConnectionString = readEnvironmentVariable('APP_INSIGHTS_CONNECTION_STRING') param appConfigurationName = readEnvironmentVariable('APP_CONFIGURATION_NAME') +param redisName = readEnvironmentVariable('REDIS_NAME') diff --git a/.azure/applications/web-api-migration-job/staging.bicepparam b/.azure/applications/web-api-migration-job/staging.bicepparam index 581936cc5..6ae2b55fe 100644 --- a/.azure/applications/web-api-migration-job/staging.bicepparam +++ b/.azure/applications/web-api-migration-job/staging.bicepparam @@ -7,3 +7,4 @@ param containerAppEnvironmentName = readEnvironmentVariable('CONTAINER_APP_ENVIR //secrets param adoConnectionStringSecretUri = readEnvironmentVariable('ADO_CONNECTION_STRING_SECRET_URI') +param redisName = readEnvironmentVariable('REDIS_NAME') diff --git a/.azure/applications/web-api-so/main.bicep b/.azure/applications/web-api-so/main.bicep index e0d3a969c..0a6b9e399 100644 --- a/.azure/applications/web-api-so/main.bicep +++ b/.azure/applications/web-api-so/main.bicep @@ -14,6 +14,9 @@ param apimIp string param containerAppEnvironmentName string @minLength(3) @secure() +param redisName string +@minLength(3) +@secure() param appInsightConnectionString string @minLength(5) @secure() @@ -33,6 +36,10 @@ resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' name: containerAppEnvironmentName } +resource redis 'Microsoft.Cache/redis@2023-08-01' existing = { + name: redisName +} + var containerAppEnvVars = [ { name: 'ASPNETCORE_ENVIRONMENT' @@ -74,6 +81,25 @@ module containerApp '../../modules/containerApp/main.bicep' = { } } +resource redisCustomAccessPolicy 'Microsoft.Cache/redis/accessPolicies@2023-08-01' = { + parent: redis + name: containerAppName + properties: { + permissions: 'Contributor' + } +} + +resource redisCustomAccessPolicyAssignment 'Microsoft.Cache/redis/accessPolicyAssignments@2023-08-01' = { + parent: redis + name: containerAppName + properties: { + accessPolicyName: containerAppName + objectId: containerApp.outputs.identityPrincipalId + objectIdAlias: '${containerAppName}-access-policy-redis' + } + dependsOn: [redisCustomAccessPolicy] +} + module keyVaultReaderAccessPolicy '../../modules/keyvault/addReaderRoles.bicep' = { name: 'keyVaultReaderAccessPolicy-${containerAppName}' params: { diff --git a/.azure/applications/web-api-so/staging.bicepparam b/.azure/applications/web-api-so/staging.bicepparam index 447dcb2be..5b4a8010a 100644 --- a/.azure/applications/web-api-so/staging.bicepparam +++ b/.azure/applications/web-api-so/staging.bicepparam @@ -10,3 +10,4 @@ param environmentKeyVaultName = readEnvironmentVariable('ENVIRONMENT_KEY_VAULT_N param containerAppEnvironmentName = readEnvironmentVariable('CONTAINER_APP_ENVIRONMENT_NAME') param appInsightConnectionString = readEnvironmentVariable('APP_INSIGHTS_CONNECTION_STRING') param appConfigurationName = readEnvironmentVariable('APP_CONFIGURATION_NAME') +param redisName = readEnvironmentVariable('REDIS_NAME') diff --git a/.azure/applications/web-api-so/test.bicepparam b/.azure/applications/web-api-so/test.bicepparam index 9df5c026b..e40c26f7f 100644 --- a/.azure/applications/web-api-so/test.bicepparam +++ b/.azure/applications/web-api-so/test.bicepparam @@ -10,3 +10,4 @@ param environmentKeyVaultName = readEnvironmentVariable('ENVIRONMENT_KEY_VAULT_N param containerAppEnvironmentName = readEnvironmentVariable('CONTAINER_APP_ENVIRONMENT_NAME') param appInsightConnectionString = readEnvironmentVariable('APP_INSIGHTS_CONNECTION_STRING') param appConfigurationName = readEnvironmentVariable('APP_CONFIGURATION_NAME') +param redisName = readEnvironmentVariable('REDIS_NAME') diff --git a/.azure/infrastructure/main.bicep b/.azure/infrastructure/main.bicep index cb1a7a642..adcab5956 100644 --- a/.azure/infrastructure/main.bicep +++ b/.azure/infrastructure/main.bicep @@ -34,6 +34,11 @@ param slackNotifierSku SlackNotifierSku import {Sku as PostgresSku} from '../modules/postgreSql/create.bicep' param postgresSku PostgresSku +import {Sku as RedisSku} from '../modules/redis/main.bicep' +param redisSku RedisSku +@minLength(1) +param redisVersion string + var secrets = { dialogportenPgAdminPassword: dialogportenPgAdminPassword sourceKeyVaultSubscriptionId: sourceKeyVaultSubscriptionId @@ -49,7 +54,7 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { location: location } -module keyVaultModule '../modules/keyvault/create.bicep' = { +module environmentKeyVault '../modules/keyvault/create.bicep' = { scope: resourceGroup name: 'keyVault' params: { @@ -104,7 +109,7 @@ module postgresql '../modules/postgreSql/create.bicep' = { params: { namePrefix: namePrefix location: location - keyVaultName: keyVaultModule.outputs.name + environmentKeyVaultName: environmentKeyVault.outputs.name srcKeyVault: srcKeyVault srcSecretName: 'dialogportenPgAdminPassword${environment}' administratorLoginPassword: contains(keyVaultSourceKeys, 'dialogportenPgAdminPassword${environment}') ? srcKeyVaultResource.getSecret('dialogportenPgAdminPassword${environment}') : secrets.dialogportenPgAdminPassword @@ -112,6 +117,18 @@ module postgresql '../modules/postgreSql/create.bicep' = { } } +module redis '../modules/redis/main.bicep' = { + scope: resourceGroup + name: 'redis' + params: { + namePrefix: namePrefix + location: location + environmentKeyVaultName: environmentKeyVault.outputs.name + sku: redisSku + version: redisVersion + } +} + module copyEnvironmentSecrets '../modules/keyvault/copySecrets.bicep' = { scope: resourceGroup name: 'copyEnvironmentSecrets' @@ -120,7 +137,7 @@ module copyEnvironmentSecrets '../modules/keyvault/copySecrets.bicep' = { srcKeyVaultName: secrets.sourceKeyVaultName srcKeyVaultRGNName: secrets.sourceKeyVaultResourceGroup srcKeyVaultSubId: secrets.sourceKeyVaultSubscriptionId - destKeyVaultName: keyVaultModule.outputs.name + destKeyVaultName: environmentKeyVault.outputs.name secretPrefix: 'dialogporten--${environment}--' } } @@ -132,7 +149,7 @@ module copyCrossEnvironmentSecrets '../modules/keyvault/copySecrets.bicep' = { srcKeyVaultName: secrets.sourceKeyVaultName srcKeyVaultRGNName: secrets.sourceKeyVaultResourceGroup srcKeyVaultSubId: secrets.sourceKeyVaultSubscriptionId - destKeyVaultName: keyVaultModule.outputs.name + destKeyVaultName: environmentKeyVault.outputs.name secretPrefix: 'dialogporten--any--' } } @@ -142,7 +159,7 @@ module slackNotifier '../modules/functionApp/slackNotifier.bicep' = { scope: resourceGroup params: { location: location - keyVaultName: keyVaultModule.outputs.name + keyVaultName: environmentKeyVault.outputs.name namePrefix: namePrefix applicationInsightsName: appInsights.outputs.appInsightsName sku: slackNotifierSku @@ -168,7 +185,7 @@ module appInsightsReaderAccessPolicy '../modules/applicationInsights/addReaderRo } } -module appConfigConfigurations '../modules/appConfiguration/upsertKeyValue.bicep' = { +module postgresConnectionStringAppConfig '../modules/appConfiguration/upsertKeyValue.bicep' = { scope: resourceGroup name: 'AppConfig_Add_DialogDbConnectionString' params: { @@ -179,15 +196,26 @@ module appConfigConfigurations '../modules/appConfiguration/upsertKeyValue.bicep } } +module redisHostNameAppConfig '../modules/appConfiguration/upsertKeyValue.bicep' = { + scope: resourceGroup + name: 'AppConfig_Add_RedisHostName' + params: { + configStoreName: appConfiguration.outputs.name + key: 'Infrastructure:RedisHostName' + value: redis.outputs.hostName + keyValueType: 'keyVaultReference' + } +} + module keyVaultReaderAccessPolicy '../modules/keyvault/addReaderRoles.bicep' = { scope: resourceGroup name: 'keyVaultReaderAccessPolicyFunctions' params: { - keyvaultName: keyVaultModule.outputs.name + keyvaultName: environmentKeyVault.outputs.name principalIds: [ slackNotifier.outputs.functionAppPrincipalId ] } } output resourceGroupName string = resourceGroup.name output containerAppEnvId string = containerAppEnv.outputs.containerAppEnvId -output environmentKeyVaultName string = keyVaultModule.outputs.name +output environmentKeyVaultName string = environmentKeyVault.outputs.name diff --git a/.azure/infrastructure/production.bicepparam b/.azure/infrastructure/production.bicepparam index 63a21d428..320a69244 100644 --- a/.azure/infrastructure/production.bicepparam +++ b/.azure/infrastructure/production.bicepparam @@ -4,6 +4,8 @@ param environment = 'production' param location = 'norwayeast' param keyVaultSourceKeys = json(readEnvironmentVariable('KEY_VAULT_SOURCE_KEYS')) +param redisVersion = '6.0' + // secrets param dialogportenPgAdminPassword = readEnvironmentVariable('PG_ADMIN_PASSWORD') param sourceKeyVaultSubscriptionId = readEnvironmentVariable('SOURCE_KEY_VAULT_SUBSCRIPTION_ID') @@ -30,3 +32,9 @@ param postgresSku = { name: 'Standard_B1ms' tier: 'Burstable' } + +param redisSku = { + name: 'Basic' + family: 'C' + capacity: 1 +} diff --git a/.azure/infrastructure/soak.bicepparam b/.azure/infrastructure/soak.bicepparam index 609096cf1..dd737f9c1 100644 --- a/.azure/infrastructure/soak.bicepparam +++ b/.azure/infrastructure/soak.bicepparam @@ -4,6 +4,8 @@ param environment = 'soak' param location = 'norwayeast' param keyVaultSourceKeys = json(readEnvironmentVariable('KEY_VAULT_SOURCE_KEYS')) +param redisVersion = '6.0' + // secrets param dialogportenPgAdminPassword = readEnvironmentVariable('PG_ADMIN_PASSWORD') param sourceKeyVaultSubscriptionId = readEnvironmentVariable('SOURCE_KEY_VAULT_SUBSCRIPTION_ID') @@ -30,3 +32,9 @@ param postgresSku = { name: 'Standard_B1ms' tier: 'Burstable' } + +param redisSku = { + name: 'Basic' + family: 'C' + capacity: 1 +} diff --git a/.azure/infrastructure/staging.bicepparam b/.azure/infrastructure/staging.bicepparam index 07b54f198..2f8855a55 100644 --- a/.azure/infrastructure/staging.bicepparam +++ b/.azure/infrastructure/staging.bicepparam @@ -4,6 +4,8 @@ param environment = 'staging' param location = 'norwayeast' param keyVaultSourceKeys = json(readEnvironmentVariable('KEY_VAULT_SOURCE_KEYS')) +param redisVersion = '6.0' + // secrets param dialogportenPgAdminPassword = readEnvironmentVariable('PG_ADMIN_PASSWORD') param sourceKeyVaultSubscriptionId = readEnvironmentVariable('SOURCE_KEY_VAULT_SUBSCRIPTION_ID') @@ -30,3 +32,9 @@ param postgresSku = { name: 'Standard_B1ms' tier: 'Burstable' } + +param redisSku = { + name: 'Basic' + family: 'C' + capacity: 1 +} diff --git a/.azure/infrastructure/test.bicepparam b/.azure/infrastructure/test.bicepparam index 19b5e658c..fa52bb031 100644 --- a/.azure/infrastructure/test.bicepparam +++ b/.azure/infrastructure/test.bicepparam @@ -4,6 +4,8 @@ param environment = 'test' param location = 'norwayeast' param keyVaultSourceKeys = json(readEnvironmentVariable('KEY_VAULT_SOURCE_KEYS')) +param redisVersion = '6.0' + // secrets param dialogportenPgAdminPassword = readEnvironmentVariable('PG_ADMIN_PASSWORD') param sourceKeyVaultSubscriptionId = readEnvironmentVariable('SOURCE_KEY_VAULT_SUBSCRIPTION_ID') @@ -30,3 +32,9 @@ param postgresSku = { name: 'Standard_B1ms' tier: 'Burstable' } + +param redisSku = { + name: 'Basic' + family: 'C' + capacity: 1 +} diff --git a/.azure/modules/postgreSql/create.bicep b/.azure/modules/postgreSql/create.bicep index 37e2301e3..cdd4de08e 100644 --- a/.azure/modules/postgreSql/create.bicep +++ b/.azure/modules/postgreSql/create.bicep @@ -1,6 +1,6 @@ param namePrefix string param location string -param keyVaultName string +param environmentKeyVaultName string param srcSecretName string @export() @@ -81,7 +81,7 @@ resource postgres 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { module adoConnectionString '../keyvault/upsertSecret.bicep' = { name: 'adoConnectionString' params: { - destKeyVaultName: keyVaultName + destKeyVaultName: environmentKeyVaultName secretName: 'dialogportenAdoConnectionString' secretValue: 'Server=${postgres.properties.fullyQualifiedDomainName};Database=${databaseName};Port=5432;User Id=${administratorLogin};Password=${administratorLoginPassword};Ssl Mode=Require;Trust Server Certificate=true;' } @@ -90,7 +90,7 @@ module adoConnectionString '../keyvault/upsertSecret.bicep' = { module psqlConnectionString '../keyvault/upsertSecret.bicep' = { name: 'psqlConnectionString' params: { - destKeyVaultName: keyVaultName + destKeyVaultName: environmentKeyVaultName secretName: 'dialogportenPsqlConnectionString' secretValue: 'psql \'host=${postgres.properties.fullyQualifiedDomainName} port=5432 dbname=${databaseName} user=${administratorLogin} password=${administratorLoginPassword} sslmode=require\'' } diff --git a/.azure/modules/redis/main.bicep b/.azure/modules/redis/main.bicep new file mode 100644 index 000000000..1979abf7d --- /dev/null +++ b/.azure/modules/redis/main.bicep @@ -0,0 +1,45 @@ +param namePrefix string +param location string +@minLength(1) +param environmentKeyVaultName string +@minLength(1) +param version string + +@export() +type Sku = { + name: 'Basic' | 'Standard' | 'Premium' + family: 'C' | 'P' + @minValue(1) + capacity: int +} +param sku Sku + +// https://learn.microsoft.com/en-us/azure/templates/microsoft.cache/redis?pivots=deployment-language-bicep +resource redis 'Microsoft.Cache/Redis@2023-08-01' = { + name: '${namePrefix}-redis' + location: location + identity: { + type: 'SystemAssigned' + } + properties: { + sku: sku + enableNonSslPort: false + redisConfiguration: { + 'aad-enabled': 'true' + 'maxmemory-policy': 'allkeys-lru' + } + redisVersion: version + } +} + +module redisConnectionString '../keyvault/upsertSecret.bicep' = { + name: 'redisHostName' + params: { + destKeyVaultName: environmentKeyVaultName + secretName: 'dialogportenRedisHostName' + // disable public access? Use vnet here maybe? + secretValue: redis.properties.hostName + } +} + +output hostName string = redis.properties.hostName diff --git a/.github/workflows/action-deploy-apps.yml b/.github/workflows/action-deploy-apps.yml index 0e2aedc70..2263ce31d 100644 --- a/.github/workflows/action-deploy-apps.yml +++ b/.github/workflows/action-deploy-apps.yml @@ -22,6 +22,8 @@ on: required: true AZURE_ADO_CONNECTION_STRING_SECRET_URI: required: true + AZURE_REDIS_NAME: + required: true inputs: region: @@ -147,6 +149,7 @@ jobs: APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }} + REDIS_NAME: ${{ secrets.AZURE_REDIS_NAME }} with: scope: resourcegroup template: ./.azure/applications/${{ matrix.name }}/main.bicep diff --git a/.github/workflows/ci-cd-main.yml b/.github/workflows/ci-cd-main.yml index 369290770..f9f4bebba 100644 --- a/.github/workflows/ci-cd-main.yml +++ b/.github/workflows/ci-cd-main.yml @@ -103,6 +103,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_REDIS_NAME: ${{ secrets.AZURE_REDIS_NAME }} with: environment: test region: norwayeast diff --git a/.github/workflows/ci-cd-staging.yml b/.github/workflows/ci-cd-staging.yml index 7c33213b3..82734ad46 100644 --- a/.github/workflows/ci-cd-staging.yml +++ b/.github/workflows/ci-cd-staging.yml @@ -64,6 +64,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_REDIS_NAME: ${{ secrets.AZURE_REDIS_NAME }} with: environment: staging region: norwayeast