From 99499a76fb0914e38c63ac0a05f5c14f998dc4a7 Mon Sep 17 00:00:00 2001 From: Shahe Islam Date: Thu, 9 Apr 2026 12:14:30 +0100 Subject: [PATCH 1/4] Add DFE Analytics Module - Adding the DFE Analytics workflow - GCP Auth scaffolding with project ID - Disabled by default, team can enable by setting the flag in tfvars --- .github/workflows/build-and-deploy.yml | 6 +++++ terraform/application/.terraform.lock.hcl | 25 ++++++++++++++++++- terraform/application/application.tf | 25 ++++++++++--------- .../application/config/review.tfvars.json | 3 ++- terraform/application/config/test.tfvars.json | 3 ++- terraform/application/dfe_analytics.tf | 19 ++++++++++++++ terraform/application/locals.tf | 15 ++++++++++- terraform/application/variables.tf | 6 ++++- 8 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 terraform/application/dfe_analytics.tf diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 2fda7e19..4bf5dfe2 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -214,6 +214,8 @@ jobs: healthcheck: ${{ env.HEALTHCHECK_CMD }} db-seed: false smoke-test: false + gcp-wip: ${{ vars.GCP_WIP }} + gcp-project-id: ${{ vars.GCP_PROJECT_ID }} # --------------------------- # REVIEW APP DATABASE DEPLOYMENT (PR) @@ -472,6 +474,8 @@ jobs: teams-webhook-url: ${{ secrets.TEAMS_WEBHOOK_URL }} service: ${{ vars.TEAMS_MSG_SERVICE_NAME }} smoke-test: false + gcp-wip: ${{ vars.GCP_WIP }} + gcp-project-id: ${{ vars.GCP_PROJECT_ID }} manual_deploy: name: Manual deploy @@ -506,6 +510,8 @@ jobs: teams-webhook-url: ${{ secrets.TEAMS_WEBHOOK_URL }} service: ${{ vars.TEAMS_MSG_SERVICE_NAME }} smoke-test: false + gcp-wip: ${{ vars.GCP_WIP }} + gcp-project-id: ${{ vars.GCP_PROJECT_ID }} - name: Checkout (needed for workspace consistency) if: ${{ inputs.environment == 'review' && inputs.refresh-review-db }} diff --git a/terraform/application/.terraform.lock.hcl b/terraform/application/.terraform.lock.hcl index 0ec79bf4..2812bee3 100644 --- a/terraform/application/.terraform.lock.hcl +++ b/terraform/application/.terraform.lock.hcl @@ -27,8 +27,9 @@ provider "registry.terraform.io/eppo/environment" { provider "registry.terraform.io/hashicorp/azurerm" { version = "4.61.0" - constraints = "4.61.0" + constraints = ">= 4.0.0, 4.61.0" hashes = [ + "h1:QWR72Af01CxqpKllMh92/uh00HQ3hNA526N6yyLdAUY=", "h1:RovkyY2wdFihSlz0qY5BQ0sgo1LJpKF8fu/7xre624Q=", "zh:314ce5459fd4a93aada8e99a2e2528d7df60f7145da0843d182a1d48f0c98a72", "zh:428bf3b41b6310dcd5f410a4be24dbd8b4d003aa82c0ac31718a7d9004fdc30e", @@ -45,6 +46,26 @@ provider "registry.terraform.io/hashicorp/azurerm" { ] } +provider "registry.terraform.io/hashicorp/google" { + version = "6.6.0" + constraints = "6.6.0" + hashes = [ + "h1:BOwY9eXbFeMU+DC1L8RW1CfcGPIiF1rMxNAxjssqNgk=", + "zh:0c181f9b9f0ab81731e5c4c2d20b6d342244506687437dad94e279ef2a588f68", + "zh:12a4c333fc0ba670e87f09eb574e4b7da90381f9929ef7c866048b6841cc8a6a", + "zh:15c277c2052df89429051350df4bccabe4cf46068433d4d8c673820d9756fc00", + "zh:35d1663c81b81cd98d768fa7b80874b48c51b27c036a3c598a597f653374d3c8", + "zh:56b268389758d544722a342da4174c486a40ffa2a49b45a06111fe31c6c9c867", + "zh:abd3ea8c3a62928ba09ba7eb42b52f53e682bd65e92d573f1739596b5a9a67b1", + "zh:be55a328d61d9db58690db74ed43614111e1105e5e52cee15acaa062df4e233e", + "zh:ce2317ce9fd02cf14323f9e061c43a415b4ae9b3f96046460d0e6b6529a5aa6c", + "zh:d54a6d8e031c824f1de21b93c3e01ed7fec134b4ae55223d08868c6168c98e47", + "zh:d8c6e33b5467c6eb5a970adb251c4c8194af12db5388cff9d4b250294eae4daa", + "zh:f49e4cc9c0b55b3bec7da64dd698298345634a5df372228ee12aa45e57982f64", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + provider "registry.terraform.io/hashicorp/kubernetes" { version = "2.32.0" constraints = "2.32.0" @@ -70,6 +91,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.8.1" hashes = [ "h1:fdfOl1HabDT42XLH8qjmfTbVZpgQZ5lyOyOa+GQhm0w=", + "h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=", "zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4", "zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae", "zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57", @@ -88,6 +110,7 @@ provider "registry.terraform.io/hashicorp/random" { provider "registry.terraform.io/hashicorp/time" { version = "0.13.1" hashes = [ + "h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=", "h1:eSX+RgYfeumojmeJ9Y5CbnkH2NkBC+9vUEolqdQVtGw=", "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", diff --git a/terraform/application/application.tf b/terraform/application/application.tf index 6d64104a..b790a583 100644 --- a/terraform/application/application.tf +++ b/terraform/application/application.tf @@ -48,7 +48,7 @@ module "application_configuration" { # Delete for non rails apps is_rails_application = true - config_variables = { + config_variables = merge({ ENVIRONMENT_NAME = var.environment PGSSLMODE = local.postgres_ssl_mode @@ -65,18 +65,18 @@ module "application_configuration" { DsiConfiguration__ValidateAudience = "true" DsiConfiguration__ValidateLifetime = "true" DsiConfiguration__TokenExpiryMinutes = "60" - } - secret_variables = { - DATABASE_URL = module.postgres.url + }, local.federated_auth_configmap) + secret_variables = merge({ + DATABASE_URL = module.postgres.url ConnectionStrings__PostgresConnectionString = module.postgres.dotnet_connection_string - DsiConfiguration__ClientId = data.azurerm_key_vault_secret.dsi_client_id.value - DsiConfiguration__ClientSecret = data.azurerm_key_vault_secret.dsi_client_secret.value - DsiConfiguration__ApiSecret = data.azurerm_key_vault_secret.dsi_api_secret.value - DsiConfiguration__ServiceId = data.azurerm_key_vault_secret.dsi_service_id.value - DFESignInSettings__SignInUri = data.azurerm_key_vault_secret.sign_in_url.value - DFESignInSettings__HelpUri = data.azurerm_key_vault_secret.help_uri.value - StorageConnectionString = "DefaultEndpointsProtocol=https;AccountName=${module.storage.name};AccountKey=${module.storage.primary_access_key}" - } + DsiConfiguration__ClientId = data.azurerm_key_vault_secret.dsi_client_id.value + DsiConfiguration__ClientSecret = data.azurerm_key_vault_secret.dsi_client_secret.value + DsiConfiguration__ApiSecret = data.azurerm_key_vault_secret.dsi_api_secret.value + DsiConfiguration__ServiceId = data.azurerm_key_vault_secret.dsi_service_id.value + DFESignInSettings__SignInUri = data.azurerm_key_vault_secret.sign_in_url.value + DFESignInSettings__HelpUri = data.azurerm_key_vault_secret.help_uri.value + StorageConnectionString = "DefaultEndpointsProtocol=https;AccountName=${module.storage.name};AccountKey=${module.storage.primary_access_key}" + }, local.federated_auth_secrets) } module "web_application" { @@ -97,4 +97,5 @@ module "web_application" { replicas = var.replicas send_traffic_to_maintenance_page = var.send_traffic_to_maintenance_page + enable_gcp_wif = var.enable_dfe_analytics_federated_auth } diff --git a/terraform/application/config/review.tfvars.json b/terraform/application/config/review.tfvars.json index 2290755a..5f76d673 100644 --- a/terraform/application/config/review.tfvars.json +++ b/terraform/application/config/review.tfvars.json @@ -2,5 +2,6 @@ "cluster": "test", "namespace": "sip-development", "deploy_azure_backing_services": false, - "enable_postgres_ssl" : false + "enable_postgres_ssl" : false, + "enable_dfe_analytics_federated_auth": true } diff --git a/terraform/application/config/test.tfvars.json b/terraform/application/config/test.tfvars.json index 88c081cb..f3c53ed1 100644 --- a/terraform/application/config/test.tfvars.json +++ b/terraform/application/config/test.tfvars.json @@ -4,5 +4,6 @@ "enable_postgres_backup_storage": true, "replicas": 2, "enable_logit": true, - "storage_container_delete_retention_days": 7 + "storage_container_delete_retention_days": 7, + "enable_dfe_analytics_federated_auth": false } diff --git a/terraform/application/dfe_analytics.tf b/terraform/application/dfe_analytics.tf new file mode 100644 index 00000000..84590ec2 --- /dev/null +++ b/terraform/application/dfe_analytics.tf @@ -0,0 +1,19 @@ +provider "google" { + project = "school-standards" +} + +module "dfe_analytics" { + count = local.dfe_analytics_enabled ? 1 : 0 + source = "./vendor/modules/aks//aks/dfe_analytics" + + azure_resource_prefix = var.azure_resource_prefix + cluster = var.cluster + namespace = var.namespace + service_short = var.service_short + environment = local.environment + + gcp_keyring = "school-standards-key-ring" + gcp_key = "school-standards-key" + gcp_taxonomy_id = "9218536955874377223" + gcp_policy_tag_id = "1991951892101805780" +} diff --git a/terraform/application/locals.tf b/terraform/application/locals.tf index fc707a90..cd1a3e0f 100644 --- a/terraform/application/locals.tf +++ b/terraform/application/locals.tf @@ -23,6 +23,19 @@ locals { dsi_urls = local.dsi_config[local.dsi_environment] key_vault_name = "${var.azure_resource_prefix}-${var.service_short}-${var.config_short}-app-kv" - + resource_group_name = "${var.azure_resource_prefix}-${var.service_short}-${var.config_short}-rg" + + dfe_analytics_enabled = var.enable_dfe_analytics_federated_auth + + federated_auth_configmap = local.dfe_analytics_enabled ? { + DfeAnalytics__Environment = local.environment + DfeAnalytics__ProjectId = module.dfe_analytics[0].bigquery_project_id + DfeAnalytics__DatasetId = module.dfe_analytics[0].bigquery_dataset + DfeAnalytics__TableId = module.dfe_analytics[0].bigquery_table_name + } : {} + + federated_auth_secrets = local.dfe_analytics_enabled ? { + DfeAnalytics__CredentialsJson = module.dfe_analytics[0].google_cloud_credentials + } : {} } diff --git a/terraform/application/variables.tf b/terraform/application/variables.tf index 6d7ea749..d5ed7965 100644 --- a/terraform/application/variables.tf +++ b/terraform/application/variables.tf @@ -61,7 +61,7 @@ variable "azure_maintenance_window" { start_hour = number start_minute = number }) - default = null + default = null description = "Maintenance window for PostgreSQL. Day 0 = Sunday, 1 = Monday, etc." } variable "docker_image" { @@ -112,4 +112,8 @@ locals { variable "enable_logit" { default = true } +variable "enable_dfe_analytics_federated_auth" { + description = "Create the resources in Google cloud for federated authentication and enable in application" + default = false +} From cc9962e0303cb231d60445cacdc90b1d8f4b0421 Mon Sep 17 00:00:00 2001 From: Christopher Morriss Date: Sun, 12 Apr 2026 16:30:32 +0100 Subject: [PATCH 2/4] added dfe analytics middlware and associated config setings --- SAPSec.Web/Program.cs | 17 ++++++++++++++--- SAPSec.Web/SAPSec.Web.csproj | 1 + SAPSec.Web/appsettings.Development.json | 4 ++++ SAPSec.Web/appsettings.Test.json | 4 ++++ SAPSec.Web/appsettings.json | 4 ++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/SAPSec.Web/Program.cs b/SAPSec.Web/Program.cs index 914ac999..55dc8dbc 100644 --- a/SAPSec.Web/Program.cs +++ b/SAPSec.Web/Program.cs @@ -1,4 +1,6 @@ -using GovUk.Frontend.AspNetCore; +using Dfe.Analytics; +using Dfe.Analytics.AspNetCore; +using GovUk.Frontend.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Localization; @@ -126,7 +128,14 @@ public static void Main(string[] args) builder.Services.AddDependencies(); // Add custom error handler for NotFoundExceptions - builder.Services.AddExceptionHandler(); + builder.Services.AddExceptionHandler(); + + builder.Services.AddDfeAnalytics(options => + { + options.ProjectId = builder.Configuration["DfeAnalytics:ProjectId"]; + options.DatasetId = builder.Configuration["DfeAnalytics:DatasetId"]; + // options.Environment = builder.Environment.EnvironmentName; + }).AddAspNetCoreIntegration(); var app = builder.Build(); @@ -201,7 +210,9 @@ public static void Main(string[] args) app.UseAuthorization(); - app.MapHealthChecks("/healthcheck"); + app.MapHealthChecks("/healthcheck"); + + app.UseDfeAnalytics(); app.MapControllers(); app.MapRazorPages(); diff --git a/SAPSec.Web/SAPSec.Web.csproj b/SAPSec.Web/SAPSec.Web.csproj index 96366914..ae0de4fc 100644 --- a/SAPSec.Web/SAPSec.Web.csproj +++ b/SAPSec.Web/SAPSec.Web.csproj @@ -11,6 +11,7 @@ + diff --git a/SAPSec.Web/appsettings.Development.json b/SAPSec.Web/appsettings.Development.json index b68c3a44..cba39b5f 100644 --- a/SAPSec.Web/appsettings.Development.json +++ b/SAPSec.Web/appsettings.Development.json @@ -26,6 +26,10 @@ "Establishments": { "CsvPath": "..\\SAPSec.Infrastructure\\Data\\Establishments.csv" }, + "DfeAnalytics": { + "ProjectId": "school-standards", + "DatasetId": "gsii_events_test" + }, "DsiConfiguration": { "ApiUri": "https://test-api.signin.education.gov.uk", "Authority": "https://test-oidc.signin.education.gov.uk", diff --git a/SAPSec.Web/appsettings.Test.json b/SAPSec.Web/appsettings.Test.json index d249258d..75e141f3 100644 --- a/SAPSec.Web/appsettings.Test.json +++ b/SAPSec.Web/appsettings.Test.json @@ -11,6 +11,10 @@ "GoogleTagManagerPreview": "env-14", "ClarityId": "vmzvlwvw68" }, + "DfeAnalytics": { + "ProjectId": "school-standards", + "DatasetId": "gsii_events_test" + }, "DsiConfiguration": { "ApiUri": "https://test-api.signin.education.gov.uk", "Authority": "https://test-oidc.signin.education.gov.uk", diff --git a/SAPSec.Web/appsettings.json b/SAPSec.Web/appsettings.json index d9249b01..6cb8cab6 100644 --- a/SAPSec.Web/appsettings.json +++ b/SAPSec.Web/appsettings.json @@ -36,6 +36,10 @@ "Establishments": { "CsvPath": "Data/Establishments.csv" }, + "DfeAnalytics": { + "ProjectId": "school-standards", + "DatasetId": "gsii_events_production" + }, "DsiConfiguration": { "ApiUri": "https://pp-api.signin.education.gov.uk", "Authority": "https://pp-oidc.signin.education.gov.uk", From afd67ebed61e1cae372fb739df7b7ee898ead667 Mon Sep 17 00:00:00 2001 From: Christopher Morriss Date: Mon, 13 Apr 2026 10:44:52 +0100 Subject: [PATCH 3/4] removed references to configuration in dfe analytics --- SAPSec.Web/Program.cs | 24 +++++++++++++++--------- SAPSec.Web/appsettings.Development.json | 9 +++++---- SAPSec.Web/appsettings.Test.json | 8 ++++---- SAPSec.Web/appsettings.json | 8 ++++---- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/SAPSec.Web/Program.cs b/SAPSec.Web/Program.cs index 55dc8dbc..498df0cc 100644 --- a/SAPSec.Web/Program.cs +++ b/SAPSec.Web/Program.cs @@ -116,7 +116,20 @@ public static void Main(string[] args) options.RequestCultureProviders.Clear(); }); - builder.Services.AddHealthChecks(); + builder.Services.AddHealthChecks(); + + //builder.Services.AddDfeAnalytics(options => + //{ + // // options.ProjectId = builder.Configuration["DfeAnalytics:ProjectId"]; + // // options.DatasetId = builder.Configuration["DfeAnalytics:DatasetId"]; + // // options.Environment = builder.Environment.EnvironmentName; + //}).AddAspNetCoreIntegration(options => + //{ + // options.UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; + // options.TableId = "events"; + //}); + + builder.Services.AddDfeAnalytics().AddAspNetCoreIntegration(); var establishmentsCsvPath = builder.Configuration["Establishments:CsvPath"]; @@ -128,14 +141,7 @@ public static void Main(string[] args) builder.Services.AddDependencies(); // Add custom error handler for NotFoundExceptions - builder.Services.AddExceptionHandler(); - - builder.Services.AddDfeAnalytics(options => - { - options.ProjectId = builder.Configuration["DfeAnalytics:ProjectId"]; - options.DatasetId = builder.Configuration["DfeAnalytics:DatasetId"]; - // options.Environment = builder.Environment.EnvironmentName; - }).AddAspNetCoreIntegration(); + builder.Services.AddExceptionHandler(); var app = builder.Build(); diff --git a/SAPSec.Web/appsettings.Development.json b/SAPSec.Web/appsettings.Development.json index cba39b5f..4194bad5 100644 --- a/SAPSec.Web/appsettings.Development.json +++ b/SAPSec.Web/appsettings.Development.json @@ -26,10 +26,11 @@ "Establishments": { "CsvPath": "..\\SAPSec.Infrastructure\\Data\\Establishments.csv" }, - "DfeAnalytics": { - "ProjectId": "school-standards", - "DatasetId": "gsii_events_test" - }, + //"DfeAnalytics": { + // "ProjectId": "school-standards", + // "DatasetId": "gsii_events_test", + // "Environment": "development" + //}, "DsiConfiguration": { "ApiUri": "https://test-api.signin.education.gov.uk", "Authority": "https://test-oidc.signin.education.gov.uk", diff --git a/SAPSec.Web/appsettings.Test.json b/SAPSec.Web/appsettings.Test.json index 75e141f3..cf51dee6 100644 --- a/SAPSec.Web/appsettings.Test.json +++ b/SAPSec.Web/appsettings.Test.json @@ -11,10 +11,10 @@ "GoogleTagManagerPreview": "env-14", "ClarityId": "vmzvlwvw68" }, - "DfeAnalytics": { - "ProjectId": "school-standards", - "DatasetId": "gsii_events_test" - }, + //"DfeAnalytics": { + // "ProjectId": "school-standards", + // "DatasetId": "gsii_events_test" + //}, "DsiConfiguration": { "ApiUri": "https://test-api.signin.education.gov.uk", "Authority": "https://test-oidc.signin.education.gov.uk", diff --git a/SAPSec.Web/appsettings.json b/SAPSec.Web/appsettings.json index 6cb8cab6..6a5ceab9 100644 --- a/SAPSec.Web/appsettings.json +++ b/SAPSec.Web/appsettings.json @@ -36,10 +36,10 @@ "Establishments": { "CsvPath": "Data/Establishments.csv" }, - "DfeAnalytics": { - "ProjectId": "school-standards", - "DatasetId": "gsii_events_production" - }, + //"DfeAnalytics": { + // "ProjectId": "school-standards", + // "DatasetId": "gsii_events_production" + //}, "DsiConfiguration": { "ApiUri": "https://pp-api.signin.education.gov.uk", "Authority": "https://pp-oidc.signin.education.gov.uk", From e94042bedb799f5123e13ffa533b09322641a07c Mon Sep 17 00:00:00 2001 From: Christopher Morriss Date: Mon, 13 Apr 2026 13:08:32 +0100 Subject: [PATCH 4/4] enabled wif for test --- SAPSec.Web/Program.cs | 6 +++--- terraform/application/config/test.tfvars.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SAPSec.Web/Program.cs b/SAPSec.Web/Program.cs index 498df0cc..c54729f3 100644 --- a/SAPSec.Web/Program.cs +++ b/SAPSec.Web/Program.cs @@ -120,8 +120,8 @@ public static void Main(string[] args) //builder.Services.AddDfeAnalytics(options => //{ - // // options.ProjectId = builder.Configuration["DfeAnalytics:ProjectId"]; - // // options.DatasetId = builder.Configuration["DfeAnalytics:DatasetId"]; + // // options.ProjectId = builder.Configuration["DfeAnalytics:ProjectId"]; + // // options.DatasetId = builder.Configuration["DfeAnalytics:DatasetId"]; // // options.Environment = builder.Environment.EnvironmentName; //}).AddAspNetCoreIntegration(options => //{ @@ -218,7 +218,7 @@ public static void Main(string[] args) app.MapHealthChecks("/healthcheck"); - app.UseDfeAnalytics(); + app.UseDfeAnalytics(); app.MapControllers(); app.MapRazorPages(); diff --git a/terraform/application/config/test.tfvars.json b/terraform/application/config/test.tfvars.json index f3c53ed1..48724ef5 100644 --- a/terraform/application/config/test.tfvars.json +++ b/terraform/application/config/test.tfvars.json @@ -5,5 +5,5 @@ "replicas": 2, "enable_logit": true, "storage_container_delete_retention_days": 7, - "enable_dfe_analytics_federated_auth": false + "enable_dfe_analytics_federated_auth": true }