From 4b7c5aeac474a3a2039686841667d000bbdf7337 Mon Sep 17 00:00:00 2001 From: Mary Crawford <29112142+marycrawford@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:16:56 -0800 Subject: [PATCH] secure database secrets and ensure middleware access (#423) * updates to deploy-dev.yml to save all workflow artifacts including dockerbuild * draft vault module changes * rebased branch due to conflicts and push changes * finally getting the random_password to save to key vault - yippeee * revert changes to the app_service/main.tf file * update workflows to include object_id * add object_id to workflows * update database outputs and dev-env.yaml file * push dev-env.yaml changes * update the backend with the database variable * update variable values in backend application.yaml file --------- Co-authored-by: marycrawford --- .github/actions/tf-setup/action.yml | 4 ++ .github/workflows/build-deploy-frontend.yml | 1 + .github/workflows/build-deploy-ocr.yml | 1 + .github/workflows/deploy-dev.yml | 5 +++ backend/src/main/resources/application.yaml | 7 ++-- dev-env.yaml | 8 ++-- ops/terraform/main.tf | 17 ++++++++- ops/terraform/modules/app_service/main.tf | 2 +- ops/terraform/modules/app_service/outputs.tf | 6 ++- ops/terraform/modules/database/main.tf | 13 ++----- ops/terraform/modules/database/outputs.tf | 18 ++++++++- ops/terraform/modules/database/variables.tf | 6 +++ ops/terraform/modules/vault/data.tf | 1 + ops/terraform/modules/vault/main.tf | 40 ++++++++++++++++++++ ops/terraform/modules/vault/outputs.tf | 4 ++ ops/terraform/modules/vault/variables.tf | 12 ++++++ ops/terraform/providers.tf | 14 +++---- ops/terraform/variables.tf | 6 ++- 18 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 ops/terraform/modules/vault/data.tf create mode 100644 ops/terraform/modules/vault/main.tf create mode 100644 ops/terraform/modules/vault/outputs.tf create mode 100644 ops/terraform/modules/vault/variables.tf diff --git a/.github/actions/tf-setup/action.yml b/.github/actions/tf-setup/action.yml index 440809a1..2f08432b 100644 --- a/.github/actions/tf-setup/action.yml +++ b/.github/actions/tf-setup/action.yml @@ -16,6 +16,9 @@ inputs: azure-subscription-id: description: The Azure subscription_id for this environment. required: true + azure-object-id: + description: The Azure object_id for this environment. + required: true app-name: description: The name of the application being deployed in Terraform. required: true @@ -50,6 +53,7 @@ runs: ARM_CLIENT_ID: ${{ inputs.azure-client-id }} ARM_TENANT_ID: ${{ inputs.azure-tenant-id }} ARM_SUBSCRIPTION_ID: ${{ inputs.azure-subscription-id }} + ARM_OBJECT_ID: ${{ inputs.azure-object-id }} shell: bash run: | terraform init -backend-config=config/${{ inputs.deploy-env }}.config diff --git a/.github/workflows/build-deploy-frontend.yml b/.github/workflows/build-deploy-frontend.yml index 5a1432e0..e36a2ca7 100644 --- a/.github/workflows/build-deploy-frontend.yml +++ b/.github/workflows/build-deploy-frontend.yml @@ -53,6 +53,7 @@ jobs: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} - name: Upload to Azure blob storage shell: bash run: | diff --git a/.github/workflows/build-deploy-ocr.yml b/.github/workflows/build-deploy-ocr.yml index 527f9bbc..584e0905 100644 --- a/.github/workflows/build-deploy-ocr.yml +++ b/.github/workflows/build-deploy-ocr.yml @@ -70,6 +70,7 @@ jobs: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} - name: Deploy OCR-API uses: ./.github/actions/deploy-api with: diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 2c3a35c7..ab88e00b 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -123,6 +123,7 @@ jobs: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} - uses: ./.github/actions/tf-setup name: Setup this environment with Terraform with: @@ -131,6 +132,7 @@ jobs: azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} app-name: reportvision deploy-middleware: @@ -145,6 +147,7 @@ jobs: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} - name: Deploy middleware-API uses: ./.github/actions/deploy-api with: @@ -165,6 +168,7 @@ jobs: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} - name: Deploy OCR-API uses: ./.github/actions/deploy-api with: @@ -186,6 +190,7 @@ jobs: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + object-id: ${{ secrets.AZURE_OBJECT_ID }} - name: Deploy frontend uses: ./.github/actions/deploy-frontend with: diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index bb0a5aed..bf39617d 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -1,8 +1,9 @@ spring: datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:reportvision} - username: ${DB_USERNAME:postgres} - password: ${DB_PASSWORD:super_secret_password} + url: ${POSTGRES_HOST:postgres_fqdn}?sslmode=require + username: ${DB_USERNAME:postgres_user} + password: ${POSTGRES_USER:postgres_password} + name: ${POSTGRES_DB:postgres_db_name} devtools: restart: enabled: true diff --git a/dev-env.yaml b/dev-env.yaml index 1a76a244..c33aade9 100644 --- a/dev-env.yaml +++ b/dev-env.yaml @@ -30,9 +30,11 @@ services: ports: - "5432:5432" environment: - POSTGRES_DB: reportvision - POSTGRES_USER: postgres - POSTGRES_PASSWORD: super_secret_password + POSTGRES_DB: ${postgres_db_name} + POSTGRES_HOST: ${postgres_fqdn} + POSTGRES_USER: ${postgres_user} + POSTGRES_PASSWORD: ${postgres_password} + sslmode: require api: build: context: ./backend diff --git a/ops/terraform/main.tf b/ops/terraform/main.tf index 2008a9f0..fa01b6a5 100644 --- a/ops/terraform/main.tf +++ b/ops/terraform/main.tf @@ -72,7 +72,7 @@ module "middleware_api" { vnet = module.networking.network_name sku_name = var.sku_name https_only = true - depends_on = [module.networking.middlewaresubnet_id, module.networking.lbsubnet_id] + depends_on = [module.networking.middlewaresubnet_id, module.networking.lbsubnet_id] } module "ocr_api" { @@ -87,7 +87,7 @@ module "ocr_api" { vnet = module.networking.network_name sku_name = var.sku_name https_only = true - depends_on = [module.networking.ocrsubnet_id, module.networking.middlewaresubnet_id] + depends_on = [module.networking.ocrsubnet_id, module.networking.middlewaresubnet_id] } module "ocr_autoscale" { @@ -109,4 +109,17 @@ module "database" { resource_group_name = data.azurerm_resource_group.rg.name subnet = module.networking.dbsubnet_id private_dns_zone_id = module.networking.private_dns_zone_id + postgres_password = module.vault.postgres_password # Password from Vault to DB +} + +module "vault" { + source = "./modules/vault" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name + tenant_id = var.tenant_id + client_id = var.client_id + object_id = module.middleware_api.webapp_id + subscription_id = var.subscription_id + postgres_server_id = module.database.postgres_server_id + service_plan_id = module.middleware_api.service_plan_id } diff --git a/ops/terraform/modules/app_service/main.tf b/ops/terraform/modules/app_service/main.tf index 41890daa..73cb8839 100644 --- a/ops/terraform/modules/app_service/main.tf +++ b/ops/terraform/modules/app_service/main.tf @@ -38,4 +38,4 @@ resource "azurerm_linux_web_app" "linux_webapp" { action = "Allow" } } -} \ No newline at end of file +} diff --git a/ops/terraform/modules/app_service/outputs.tf b/ops/terraform/modules/app_service/outputs.tf index 20fa96b5..10ca032b 100644 --- a/ops/terraform/modules/app_service/outputs.tf +++ b/ops/terraform/modules/app_service/outputs.tf @@ -8,4 +8,8 @@ output "service_plan_id" { output "webapp_name" { value = azurerm_linux_web_app.linux_webapp.name -} \ No newline at end of file +} + +output "webapp_id" { + value = azurerm_linux_web_app.linux_webapp.identity[0].principal_id +} diff --git a/ops/terraform/modules/database/main.tf b/ops/terraform/modules/database/main.tf index 4954d77f..ce371058 100644 --- a/ops/terraform/modules/database/main.tf +++ b/ops/terraform/modules/database/main.tf @@ -7,11 +7,11 @@ resource "azurerm_postgresql_flexible_server" "postgres_flexible_server" { resource_group_name = var.resource_group_name sku_name = var.postgres_sku_name version = var.engine_version - storage_mb = 32768 # 32 GB, the lowest of the valid options + storage_mb = 32768 # 32 GiB storage for B_Standard_B1ms backup_retention_days = 7 administrator_login = var.db_username - administrator_password = random_string.setup_rds_password.result + administrator_password = var.postgres_password delegated_subnet_id = var.subnet private_dns_zone_id = var.private_dns_zone_id @@ -20,6 +20,7 @@ resource "azurerm_postgresql_flexible_server" "postgres_flexible_server" { lifecycle { prevent_destroy = true + ignore_changes = [zone] } } @@ -27,11 +28,3 @@ resource "azurerm_postgresql_flexible_server_database" "postgres_db" { name = "${azurerm_postgresql_flexible_server.postgres_flexible_server.name}-db" server_id = azurerm_postgresql_flexible_server.postgres_flexible_server.id } - -# Random string resource for the postgres password -resource "random_string" "setup_rds_password" { - length = 16 # Length of the password - - # Character set that excludes problematic characters like quotes, backslashes, etc. - override_special = "_!@#-$%^&*()[]{}" -} diff --git a/ops/terraform/modules/database/outputs.tf b/ops/terraform/modules/database/outputs.tf index 1afbb7db..dcbc9368 100644 --- a/ops/terraform/modules/database/outputs.tf +++ b/ops/terraform/modules/database/outputs.tf @@ -1,3 +1,17 @@ -output "postgres_db_password" { - value = azurerm_postgresql_flexible_server.postgres_flexible_server.administrator_login +output "postgres_server_id" { + value = azurerm_postgresql_flexible_server.postgres_flexible_server +} + +output "postgres_fqdn" { + value = azurerm_postgresql_flexible_server.postgres_flexible_server + description = "The fully qualified domain name (FQDN) of the PostgreSQL flexible server" +} + +output "postgres_user" { + value = var.db_username + description = "User name for the Application's PostgreSQL flexible server database" +} + +output "postgres_db_name" { + value = var.db_username } diff --git a/ops/terraform/modules/database/variables.tf b/ops/terraform/modules/database/variables.tf index f8587c41..6a81bd7f 100644 --- a/ops/terraform/modules/database/variables.tf +++ b/ops/terraform/modules/database/variables.tf @@ -20,6 +20,12 @@ variable "resource_group_name" { description = "The Azure Resource Group to deploy to" } +variable "postgres_password" { + description = "The password for the PostgreSQL database" + type = string + sensitive = true # This ensures Terraform treats the password as sensitive +} + variable "postgres_sku_name" { type = string description = "value" diff --git a/ops/terraform/modules/vault/data.tf b/ops/terraform/modules/vault/data.tf new file mode 100644 index 00000000..cee07df2 --- /dev/null +++ b/ops/terraform/modules/vault/data.tf @@ -0,0 +1 @@ +data "azurerm_client_config" "current" {} diff --git a/ops/terraform/modules/vault/main.tf b/ops/terraform/modules/vault/main.tf new file mode 100644 index 00000000..591c0276 --- /dev/null +++ b/ops/terraform/modules/vault/main.tf @@ -0,0 +1,40 @@ +resource "azurerm_key_vault" "this" { + name = "reportvisionvault" + location = var.location + resource_group_name = var.resource_group_name + sku_name = "standard" + tenant_id = data.azurerm_client_config.current.tenant_id + purge_protection_enabled = true + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Get", + "List", + ] + + secret_permissions = [ + "Set", + "Get", + "Recover", + "List", + ] + } +} + +# Random string resource for the postgres password +resource "random_string" "postgres_password" { + length = 16 + override_special = "_!@#-$%^&*()[]{}" # excluded characters +} + +resource "azurerm_key_vault_secret" "postgres_db_secret" { + name = "reportvision-postgres-db-password" + value = random_string.postgres_password.result + key_vault_id = azurerm_key_vault.this.id + + depends_on = [azurerm_key_vault.this] +} diff --git a/ops/terraform/modules/vault/outputs.tf b/ops/terraform/modules/vault/outputs.tf new file mode 100644 index 00000000..afc86b67 --- /dev/null +++ b/ops/terraform/modules/vault/outputs.tf @@ -0,0 +1,4 @@ +output "postgres_password" { + value = random_string.postgres_password.result + sensitive = true +} diff --git a/ops/terraform/modules/vault/variables.tf b/ops/terraform/modules/vault/variables.tf new file mode 100644 index 00000000..7738ce02 --- /dev/null +++ b/ops/terraform/modules/vault/variables.tf @@ -0,0 +1,12 @@ +variable "client_id" {} +variable "location" {} +variable "object_id" { + type = string +} +variable "postgres_server_id" { + type = string +} +variable "resource_group_name" {} +variable "subscription_id" {} +variable "service_plan_id" {} +variable "tenant_id" {} diff --git a/ops/terraform/providers.tf b/ops/terraform/providers.tf index 345c788e..8a7fb866 100644 --- a/ops/terraform/providers.tf +++ b/ops/terraform/providers.tf @@ -19,13 +19,11 @@ terraform { } provider "azurerm" { - features {} + features { + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } + } } -# # Provider for Azure Active Directory resources (e.g., service principals) -# provider "azuread" { - -# client_id = var.client_id -# tenant_id = var.tenant_id - -# } diff --git a/ops/terraform/variables.tf b/ops/terraform/variables.tf index ed8f0976..05b57c5b 100644 --- a/ops/terraform/variables.tf +++ b/ops/terraform/variables.tf @@ -1,10 +1,12 @@ +variable "client_id" {} variable "name" {} - +variable "object_id" {} +variable "tenant_id" {} variable "sku_name" { type = string description = "The Azure Stock Keep Unit (SKU) version" } - +variable "subscription_id" {} variable "resource_group_name" { description = "value of the Azure resource group to deploy to" }