diff --git a/.github/workflows/_deploy-infrastructure.yml b/.github/workflows/_deploy-infrastructure.yml index 660f9da49..22ed493fa 100644 --- a/.github/workflows/_deploy-infrastructure.yml +++ b/.github/workflows/_deploy-infrastructure.yml @@ -79,6 +79,7 @@ jobs: - name: Plan Cluster Resources id: deploy_cluster env: + POSTGRES_ADMIN_OBJECT_ID: ${{ inputs.postgres_admin_object_id }} GOOGLE_OAUTH_CLIENT_ID: ${{ vars.GOOGLE_OAUTH_CLIENT_ID }} GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }} STRIPE_PUBLISHABLE_KEY: ${{ vars.STRIPE_PUBLISHABLE_KEY }} @@ -144,6 +145,7 @@ jobs: - name: Deploy Cluster Resources id: deploy_cluster env: + POSTGRES_ADMIN_OBJECT_ID: ${{ inputs.postgres_admin_object_id }} GOOGLE_OAUTH_CLIENT_ID: ${{ vars.GOOGLE_OAUTH_CLIENT_ID }} GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }} STRIPE_PUBLISHABLE_KEY: ${{ vars.STRIPE_PUBLISHABLE_KEY }} @@ -161,9 +163,6 @@ jobs: - name: Install PostgreSQL Client run: sudo apt-get update && sudo apt-get install -y postgresql-client - - name: Add PostgreSQL Admin - run: bash ./cloud-infrastructure/cluster/add-postgres-admin.sh ${{ inputs.unique_prefix }} ${{ inputs.azure_environment }} ${{ inputs.cluster_location_acronym }} ${{ inputs.postgres_admin_object_id }} - - name: Open Firewall working-directory: cloud-infrastructure/cluster env: diff --git a/application/account/Core/Database/Migrations/20260303023200_Initial.cs b/application/account/Core/Database/Migrations/20260303023200_Initial.cs index a91a1eb09..103bfd44b 100644 --- a/application/account/Core/Database/Migrations/20260303023200_Initial.cs +++ b/application/account/Core/Database/Migrations/20260303023200_Initial.cs @@ -26,7 +26,7 @@ protected override void Up(MigrationBuilder migrationBuilder) "tenants", table => new { - id = table.Column("bigint", nullable: false), + id = table.Column("bigint", nullable: false), created_at = table.Column("timestamptz", nullable: false), modified_at = table.Column("timestamptz", nullable: true), deleted_at = table.Column("timestamptz", nullable: true), diff --git a/cloud-infrastructure/cluster/add-postgres-admin.sh b/cloud-infrastructure/cluster/add-postgres-admin.sh deleted file mode 100755 index c3825dfbc..000000000 --- a/cloud-infrastructure/cluster/add-postgres-admin.sh +++ /dev/null @@ -1,18 +0,0 @@ -set -e - -UNIQUE_PREFIX=$1 -ENVIRONMENT=$2 -CLUSTER_LOCATION_ACRONYM=$3 -POSTGRES_ADMIN_OBJECT_ID=$4 - -CLUSTER_RESOURCE_GROUP_NAME=$UNIQUE_PREFIX-$ENVIRONMENT-$CLUSTER_LOCATION_ACRONYM -POSTGRES_SERVER_NAME=$CLUSTER_RESOURCE_GROUP_NAME - -echo "$(date +"%Y-%m-%dT%H:%M:%S") Adding Entra ID group $POSTGRES_ADMIN_OBJECT_ID as admin on PostgreSQL server $POSTGRES_SERVER_NAME" - -az postgres flexible-server microsoft-entra-admin create \ - --resource-group $CLUSTER_RESOURCE_GROUP_NAME \ - --server-name $POSTGRES_SERVER_NAME \ - --display-name "PostgreSQL Admins" \ - --object-id $POSTGRES_ADMIN_OBJECT_ID \ - --type Group diff --git a/cloud-infrastructure/cluster/deploy-cluster.sh b/cloud-infrastructure/cluster/deploy-cluster.sh index 2bf396840..041bfbba1 100755 --- a/cloud-infrastructure/cluster/deploy-cluster.sh +++ b/cloud-infrastructure/cluster/deploy-cluster.sh @@ -29,6 +29,7 @@ export UNIQUE_PREFIX export ENVIRONMENT export LOCATION=$CLUSTER_LOCATION export DOMAIN_NAME +export POSTGRES_ADMIN_OBJECT_ID export GOOGLE_OAUTH_CLIENT_ID export GOOGLE_OAUTH_CLIENT_SECRET export STRIPE_PUBLISHABLE_KEY @@ -105,7 +106,6 @@ then echo "BACK_OFFICE_IDENTITY_CLIENT_ID=$BACK_OFFICE_IDENTITY_CLIENT_ID" >> $GITHUB_OUTPUT echo "MAIN_IDENTITY_CLIENT_ID=$MAIN_IDENTITY_CLIENT_ID" >> $GITHUB_OUTPUT else - . ./add-postgres-admin.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM $POSTGRES_ADMIN_OBJECT_ID . ./grant-database-permissions.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM 'account' $ACCOUNT_IDENTITY_CLIENT_ID . ./grant-database-permissions.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM 'back-office' $BACK_OFFICE_IDENTITY_CLIENT_ID . ./grant-database-permissions.sh $UNIQUE_PREFIX $ENVIRONMENT $CLUSTER_LOCATION_ACRONYM 'main' $MAIN_IDENTITY_CLIENT_ID diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index 63fd4e9ff..7b078adfe 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -15,6 +15,9 @@ param communicationServicesDataLocation string = 'europe' param mailSenderDisplayName string = 'PlatformPlatform' param revisionSuffix string +@description('Object ID of the Entra ID security group for PostgreSQL administration') +param postgresAdminObjectId string = '' + @secure() param googleOAuthClientId string @secure() @@ -101,6 +104,7 @@ module keyVault '../modules/key-vault.bicep' = { subnetId: virtualNetwork.outputs.containerAppsSubnetId storageAccountId: diagnosticStorageAccount.outputs.storageAccountId workspaceId: existingLogAnalyticsWorkspace.id + domainName: domainName } } @@ -153,6 +157,7 @@ module postgresServer '../modules/postgresql-flexible-server.bicep' = { virtualNetworkId: virtualNetwork.outputs.virtualNetworkId isProduction: environment == 'prod' diagnosticStorageAccountId: diagnosticStorageAccount.outputs.storageAccountId + dbAdminObjectId: postgresAdminObjectId } } diff --git a/cloud-infrastructure/cluster/main-cluster.bicepparam b/cloud-infrastructure/cluster/main-cluster.bicepparam index 41810adea..60f95ed6a 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicepparam +++ b/cloud-infrastructure/cluster/main-cluster.bicepparam @@ -13,6 +13,7 @@ param backOfficeVersion = readEnvironmentVariable('BACK_OFFICE_VERSION') param mainVersion = readEnvironmentVariable('MAIN_VERSION') param applicationInsightsConnectionString = readEnvironmentVariable('APPLICATIONINSIGHTS_CONNECTION_STRING') param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') +param postgresAdminObjectId = readEnvironmentVariable('POSTGRES_ADMIN_OBJECT_ID', '') param googleOAuthClientId = readEnvironmentVariable('GOOGLE_OAUTH_CLIENT_ID', '') param googleOAuthClientSecret = readEnvironmentVariable('GOOGLE_OAUTH_CLIENT_SECRET', '') param stripePublishableKey = readEnvironmentVariable('STRIPE_PUBLISHABLE_KEY', '') diff --git a/cloud-infrastructure/modules/key-vault.bicep b/cloud-infrastructure/modules/key-vault.bicep index 5d92a18f7..1995a9244 100644 --- a/cloud-infrastructure/modules/key-vault.bicep +++ b/cloud-infrastructure/modules/key-vault.bicep @@ -5,6 +5,7 @@ param tenantId string param subnetId string param storageAccountId string param workspaceId string +param domainName string = '' resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = { name: name @@ -119,11 +120,13 @@ resource authenticationTokenSigningKey 'Microsoft.KeyVault/vaults/keys@2023-07-0 } } +var tokenIssuerAndAudience = domainName != '' ? 'https://${domainName}' : 'PlatformPlatform' + resource authenticationTokenIssuer 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { parent: keyVault name: 'authentication-token-issuer' properties: { - value: 'PlatformPlatform' // Consider using the domain name (https://app.your-company.net) or company name (Your Company) as the issuer + value: tokenIssuerAndAudience } } @@ -131,7 +134,7 @@ resource authenticationTokenAudience 'Microsoft.KeyVault/vaults/secrets@2023-07- parent: keyVault name: 'authentication-token-audience' properties: { - value: 'PlatformPlatform' // Consider using the domain name (https://product.your-company.net) or product name (product-name) as the audience + value: tokenIssuerAndAudience } } diff --git a/cloud-infrastructure/modules/postgresql-flexible-server.bicep b/cloud-infrastructure/modules/postgresql-flexible-server.bicep index 9c7d4df81..f3a28ec6a 100644 --- a/cloud-infrastructure/modules/postgresql-flexible-server.bicep +++ b/cloud-infrastructure/modules/postgresql-flexible-server.bicep @@ -6,6 +6,8 @@ param subnetId string param virtualNetworkId string param isProduction bool param diagnosticStorageAccountId string +@description('Object ID of the Entra ID security group to assign as PostgreSQL administrator') +param dbAdminObjectId string = '' resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2025-08-01' = { name: name @@ -44,6 +46,16 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2025-08-01' = } } +resource postgresServerAdministrator 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2025-08-01' = if (!empty(dbAdminObjectId)) { + parent: postgresServer + name: dbAdminObjectId + properties: { + principalName: 'PostgreSQL Admins - ${isProduction ? 'Production' : 'Staging'}' + principalType: 'Group' + tenantId: tenantId + } +} + resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = { name: 'privatelink.postgres.database.azure.com' location: 'global'