Skip to content

Commit 802762e

Browse files
authored
Fix Postgres infrastructure and JWT token issuer configuration (#865)
### Summary & Motivation Clean up PostgreSQL infrastructure provisioning and fix JWT token signing to use the actual deployment domain. - Move Entra ID admin provisioning for PostgreSQL into Bicep, replacing the separate `add-postgres-admin.sh` shell script that ran as a post-deployment step. The admin is now declared as a resource on the flexible server, removing the need for a separate post-deployment script - Fix cosmetic type mismatch for the tenant id column in the account initial migration where the C# generic type was `string` but the column type is `bigint`, corrected to `long` - Use the custom domain (e.g., `https://your-domain.com`) as the JWT token issuer and audience instead of a hardcoded value. The previous `iss` and `aud` claims were not a valid URL, which caused JWT signature verification to fail on tools like jwt.io. The domain is now passed from the cluster deployment into the Key Vault module, falling back to a hardcoded value when no custom domain is configured ### Downstream projects The JWT issuer/audience change only takes effect when a custom domain is configured before the first deployment. Bicep does not overwrite existing Key Vault secrets, so existing deployments will keep the old values. To update an existing deployment, run the following from the Azure Portal Cloud Shell (Bash): ```bash az account set --subscription <subscription-id> VAULT_NAME="<key-vault-name>" DOMAIN="https://your-domain.com" USER_ID=$(az ad signed-in-user show --query id -o tsv) VAULT_ID=$(az keyvault show --name "$VAULT_NAME" --query id -o tsv) MY_IP=$(curl -s ifconfig.me) az role assignment create --role "Key Vault Secrets Officer" --assignee "$USER_ID" --scope "$VAULT_ID" az keyvault network-rule add --name "$VAULT_NAME" --ip-address "$MY_IP/32" echo "Waiting 60 seconds for RBAC propagation..." sleep 60 az keyvault secret set --vault-name "$VAULT_NAME" --name authentication-token-issuer --value "$DOMAIN" az keyvault secret set --vault-name "$VAULT_NAME" --name authentication-token-audience --value "$DOMAIN" az keyvault network-rule remove --name "$VAULT_NAME" --ip-address "$MY_IP/32" az role assignment delete --role "Key Vault Secrets Officer" --assignee "$USER_ID" --scope "$VAULT_ID" ``` The Key Vault name matches the cluster resource group name. ### Checklist - [x] I have added tests, or done manual regression tests - [x] I have updated the documentation, if necessary
2 parents 310887b + c4b8219 commit 802762e

File tree

8 files changed

+27
-25
lines changed

8 files changed

+27
-25
lines changed

.github/workflows/_deploy-infrastructure.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ jobs:
7979
- name: Plan Cluster Resources
8080
id: deploy_cluster
8181
env:
82+
POSTGRES_ADMIN_OBJECT_ID: ${{ inputs.postgres_admin_object_id }}
8283
GOOGLE_OAUTH_CLIENT_ID: ${{ vars.GOOGLE_OAUTH_CLIENT_ID }}
8384
GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }}
8485
STRIPE_PUBLISHABLE_KEY: ${{ vars.STRIPE_PUBLISHABLE_KEY }}
@@ -144,6 +145,7 @@ jobs:
144145
- name: Deploy Cluster Resources
145146
id: deploy_cluster
146147
env:
148+
POSTGRES_ADMIN_OBJECT_ID: ${{ inputs.postgres_admin_object_id }}
147149
GOOGLE_OAUTH_CLIENT_ID: ${{ vars.GOOGLE_OAUTH_CLIENT_ID }}
148150
GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }}
149151
STRIPE_PUBLISHABLE_KEY: ${{ vars.STRIPE_PUBLISHABLE_KEY }}
@@ -161,9 +163,6 @@ jobs:
161163
- name: Install PostgreSQL Client
162164
run: sudo apt-get update && sudo apt-get install -y postgresql-client
163165

164-
- name: Add PostgreSQL Admin
165-
run: bash ./cloud-infrastructure/cluster/add-postgres-admin.sh ${{ inputs.unique_prefix }} ${{ inputs.azure_environment }} ${{ inputs.cluster_location_acronym }} ${{ inputs.postgres_admin_object_id }}
166-
167166
- name: Open Firewall
168167
working-directory: cloud-infrastructure/cluster
169168
env:

application/account/Core/Database/Migrations/20260303023200_Initial.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
2626
"tenants",
2727
table => new
2828
{
29-
id = table.Column<string>("bigint", nullable: false),
29+
id = table.Column<long>("bigint", nullable: false),
3030
created_at = table.Column<DateTimeOffset>("timestamptz", nullable: false),
3131
modified_at = table.Column<DateTimeOffset>("timestamptz", nullable: true),
3232
deleted_at = table.Column<DateTimeOffset>("timestamptz", nullable: true),

cloud-infrastructure/cluster/add-postgres-admin.sh

Lines changed: 0 additions & 18 deletions
This file was deleted.

cloud-infrastructure/cluster/deploy-cluster.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export UNIQUE_PREFIX
2929
export ENVIRONMENT
3030
export LOCATION=$CLUSTER_LOCATION
3131
export DOMAIN_NAME
32+
export POSTGRES_ADMIN_OBJECT_ID
3233
export GOOGLE_OAUTH_CLIENT_ID
3334
export GOOGLE_OAUTH_CLIENT_SECRET
3435
export STRIPE_PUBLISHABLE_KEY
@@ -105,7 +106,6 @@ then
105106
echo "BACK_OFFICE_IDENTITY_CLIENT_ID=$BACK_OFFICE_IDENTITY_CLIENT_ID" >> $GITHUB_OUTPUT
106107
echo "MAIN_IDENTITY_CLIENT_ID=$MAIN_IDENTITY_CLIENT_ID" >> $GITHUB_OUTPUT
107108
else
108-
. ./add-postgres-admin.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM $POSTGRES_ADMIN_OBJECT_ID
109109
. ./grant-database-permissions.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM 'account' $ACCOUNT_IDENTITY_CLIENT_ID
110110
. ./grant-database-permissions.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM 'back-office' $BACK_OFFICE_IDENTITY_CLIENT_ID
111111
. ./grant-database-permissions.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM 'main' $MAIN_IDENTITY_CLIENT_ID

cloud-infrastructure/cluster/main-cluster.bicep

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ param communicationServicesDataLocation string = 'europe'
1515
param mailSenderDisplayName string = 'PlatformPlatform'
1616
param revisionSuffix string
1717

18+
@description('Object ID of the Entra ID security group for PostgreSQL administration')
19+
param postgresAdminObjectId string = ''
20+
1821
@secure()
1922
param googleOAuthClientId string
2023
@secure()
@@ -101,6 +104,7 @@ module keyVault '../modules/key-vault.bicep' = {
101104
subnetId: virtualNetwork.outputs.containerAppsSubnetId
102105
storageAccountId: diagnosticStorageAccount.outputs.storageAccountId
103106
workspaceId: existingLogAnalyticsWorkspace.id
107+
domainName: domainName
104108
}
105109
}
106110

@@ -153,6 +157,7 @@ module postgresServer '../modules/postgresql-flexible-server.bicep' = {
153157
virtualNetworkId: virtualNetwork.outputs.virtualNetworkId
154158
isProduction: environment == 'prod'
155159
diagnosticStorageAccountId: diagnosticStorageAccount.outputs.storageAccountId
160+
dbAdminObjectId: postgresAdminObjectId
156161
}
157162
}
158163

cloud-infrastructure/cluster/main-cluster.bicepparam

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ param backOfficeVersion = readEnvironmentVariable('BACK_OFFICE_VERSION')
1313
param mainVersion = readEnvironmentVariable('MAIN_VERSION')
1414
param applicationInsightsConnectionString = readEnvironmentVariable('APPLICATIONINSIGHTS_CONNECTION_STRING')
1515
param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX')
16+
param postgresAdminObjectId = readEnvironmentVariable('POSTGRES_ADMIN_OBJECT_ID', '')
1617
param googleOAuthClientId = readEnvironmentVariable('GOOGLE_OAUTH_CLIENT_ID', '')
1718
param googleOAuthClientSecret = readEnvironmentVariable('GOOGLE_OAUTH_CLIENT_SECRET', '')
1819
param stripePublishableKey = readEnvironmentVariable('STRIPE_PUBLISHABLE_KEY', '')

cloud-infrastructure/modules/key-vault.bicep

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ param tenantId string
55
param subnetId string
66
param storageAccountId string
77
param workspaceId string
8+
param domainName string = ''
89

910
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
1011
name: name
@@ -119,19 +120,21 @@ resource authenticationTokenSigningKey 'Microsoft.KeyVault/vaults/keys@2023-07-0
119120
}
120121
}
121122

123+
var tokenIssuerAndAudience = domainName != '' ? 'https://${domainName}' : 'PlatformPlatform'
124+
122125
resource authenticationTokenIssuer 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
123126
parent: keyVault
124127
name: 'authentication-token-issuer'
125128
properties: {
126-
value: 'PlatformPlatform' // Consider using the domain name (https://app.your-company.net) or company name (Your Company) as the issuer
129+
value: tokenIssuerAndAudience
127130
}
128131
}
129132

130133
resource authenticationTokenAudience 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
131134
parent: keyVault
132135
name: 'authentication-token-audience'
133136
properties: {
134-
value: 'PlatformPlatform' // Consider using the domain name (https://product.your-company.net) or product name (product-name) as the audience
137+
value: tokenIssuerAndAudience
135138
}
136139
}
137140

cloud-infrastructure/modules/postgresql-flexible-server.bicep

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ param subnetId string
66
param virtualNetworkId string
77
param isProduction bool
88
param diagnosticStorageAccountId string
9+
@description('Object ID of the Entra ID security group to assign as PostgreSQL administrator')
10+
param dbAdminObjectId string = ''
911

1012
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2025-08-01' = {
1113
name: name
@@ -44,6 +46,16 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2025-08-01' =
4446
}
4547
}
4648

49+
resource postgresServerAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2025-08-01' = if (!empty(dbAdminObjectId)) {
50+
parent: postgresServer
51+
name: dbAdminObjectId
52+
properties: {
53+
principalName: 'PostgreSQL Admins - ${isProduction ? 'Production' : 'Staging'}'
54+
principalType: 'Group'
55+
tenantId: tenantId
56+
}
57+
}
58+
4759
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
4860
name: 'privatelink.postgres.database.azure.com'
4961
location: 'global'

0 commit comments

Comments
 (0)