Skip to content

Commit

Permalink
Feat: specify app settings as code (#1294)
Browse files Browse the repository at this point in the history
  • Loading branch information
angela-tran committed Mar 7, 2023
2 parents f6e6682 + 3e9a2f7 commit 5d1fa35
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/deployment/infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Within the `CDT Digital CA` directory ([how to switch](https://learn.microsoft.c

All resources in these Resource Groups should be reflected in Terraform in this repository. The exceptions are:

- Secrets, such as values under [Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) and [App Service application settings](https://docs.microsoft.com/en-us/azure/app-service/configure-common#configure-app-settings). [`prevent_destroy`](https://developer.hashicorp.com/terraform/tutorials/state/resource-lifecycle#prevent-resource-deletion) is used on these Resources.
- Secrets, such as values under [Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). [`prevent_destroy`](https://developer.hashicorp.com/terraform/tutorials/state/resource-lifecycle#prevent-resource-deletion) is used on these Resources.
- [Things managed by DevSecOps](#ownership)

You'll see these referenced in Terraform as [data sources](https://developer.hashicorp.com/terraform/language/data-sources).
Expand Down
20 changes: 20 additions & 0 deletions docs/deployment/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,23 @@ To set a secret by providing the path of a file containing the secret (useful fo
```bash
./file.sh <environment_letter> <secret_name> <file_path>
```

To verify the value of a secret, you can use the helper script named `read.sh`.

```bash
./read.sh <environment_letter> <secret_name>
```

## Refreshing secrets

To make sure the Benefits application uses the latest secret values in Key Vault, you will need to make a change to the app service's configuration. If you don't do this step, the application will instead use cached values, which may not be what you expect. See the [Azure docs](https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?tabs=azure-cli#rotation) for more details.

The steps are:

1. After setting new secret values, go to the App Service configuration in Azure Portal, and change the value of the setting named `change_me_to_refresh_secrets`.
1. Save your changes.

The effects of following those steps should be:

- A restart of the App Service is triggered.
- The next time that our Azure infrastructure pipeline is run, the value of `change_me_to_refresh_secrets` is set back to the value defined in our Terraform file for the App Service resource.
2 changes: 1 addition & 1 deletion docs/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ docker compose down
[docker]: https://www.docker.com/products/docker-desktop
[sample-data]: https://github.com/cal-itp/benefits/tree/dev/benefits/core/migrations/0002_sample_data.py

[data-migration](https://github.com/cal-itp/benefits/tree/dev/benefits/core/migrations)
[data-migration]: (https://github.com/cal-itp/benefits/tree/dev/benefits/core/migrations)
81 changes: 79 additions & 2 deletions terraform/app_service.tf
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,87 @@ resource "azurerm_linux_web_app" "main" {
mount_path = "/home/calitp/app/config"
}

app_settings = {
# app setting used solely for refreshing secrets - see https://github.com/MicrosoftDocs/azure-docs/issues/79855#issuecomment-1265664801
"change_me_to_refresh_secrets" = "change me in the portal to refresh all secrets",

"DOCKER_ENABLE_CI" = "true",
"DOCKER_REGISTRY_SERVER_URL" = "https://ghcr.io/",
"WEBSITE_HTTPLOGGING_RETENTION_DAYS" = "99999",
"WEBSITE_TIME_ZONE" = "America/Los_Angeles",
"WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false",
"WEBSITES_PORT" = "8000",

"ANALYTICS_KEY" = local.is_dev ? null : "${local.secret_prefix}analytics-key)",

# Django settings
"DJANGO_ADMIN" = (local.is_prod || local.is_test) ? null : "${local.secret_prefix}django-admin)",
"DJANGO_ALLOWED_HOSTS" = "${local.secret_prefix}django-allowed-hosts)",
"DJANGO_DEBUG" = local.is_prod ? null : "${local.secret_prefix}django-debug)",
"DJANGO_LOAD_SAMPLE_DATA" = "false",
"DJANGO_LOG_LEVEL" = "${local.secret_prefix}django-log-level)",
"DJANGO_MIGRATIONS_DIR" = "./config",

"DJANGO_RATE_LIMIT" = local.is_dev ? null : "${local.secret_prefix}django-rate-limit)",
"DJANGO_RATE_LIMIT_METHODS" = local.is_dev ? null : "${local.secret_prefix}django-rate-limit-methods)",
"DJANGO_RATE_LIMIT_PERIOD" = local.is_dev ? null : "${local.secret_prefix}django-rate-limit-period)",

"DJANGO_RECAPTCHA_SECRET_KEY" = local.is_dev ? null : "${local.secret_prefix}django-recaptcha-secret-key)",
"DJANGO_RECAPTCHA_SITE_KEY" = local.is_dev ? null : "${local.secret_prefix}django-recaptcha-site-key)",

"DJANGO_SECRET_KEY" = "${local.secret_prefix}django-secret-key)",
"DJANGO_TRUSTED_ORIGINS" = "${local.secret_prefix}django-trusted-origins)",

"HEALTHCHECK_USER_AGENTS" = local.is_dev ? null : "${local.secret_prefix}healthcheck-user-agents)",

# Sentry
"SENTRY_DSN" = "${local.secret_prefix}sentry-dsn)",
"SENTRY_ENVIRONMENT" = local.env_name,

# Environment variables for data migration
"MST_SENIOR_GROUP_ID" = "${local.secret_prefix}mst-senior-group-id)",
"MST_COURTESY_CARD_GROUP_ID" = "${local.secret_prefix}mst-courtesy-card-group-id)"
"SACRT_SENIOR_GROUP_ID" = "${local.secret_prefix}sacrt-senior-group-id)"
"CLIENT_PRIVATE_KEY" = "${local.secret_prefix}client-private-key)"
"CLIENT_PUBLIC_KEY" = "${local.secret_prefix}client-public-key)"
"SERVER_PUBLIC_KEY_URL" = "${local.secret_prefix}server-public-key-url)"
"PAYMENT_PROCESSOR_CLIENT_CERT" = "${local.secret_prefix}payment-processor-client-cert)"
"PAYMENT_PROCESSOR_CLIENT_CERT_PRIVATE_KEY" = "${local.secret_prefix}payment-processor-client-cert-private-key)"
"PAYMENT_PROCESSOR_CLIENT_CERT_ROOT_CA" = "${local.secret_prefix}payment-processor-client-cert-root-ca)"
"AUTH_PROVIDER_CLIENT_NAME" = "${local.secret_prefix}auth-provider-client-name)"
"AUTH_PROVIDER_CLIENT_ID" = "${local.secret_prefix}auth-provider-client-id)"
"AUTH_PROVIDER_AUTHORITY" = "${local.secret_prefix}auth-provider-authority)"
"AUTH_PROVIDER_SCOPE" = "${local.secret_prefix}auth-provider-scope)"
"AUTH_PROVIDER_CLAIM" = "${local.secret_prefix}auth-provider-claim)"
"MST_OAUTH_VERIFIER_NAME" = "${local.secret_prefix}mst-oauth-verifier-name)"
"COURTESY_CARD_VERIFIER" = "${local.secret_prefix}courtesy-card-verifier)"
"COURTESY_CARD_VERIFIER_API_URL" = "${local.secret_prefix}courtesy-card-verifier-api-url)"
"COURTESY_CARD_VERIFIER_API_AUTH_HEADER" = "${local.secret_prefix}courtesy-card-verifier-api-auth-header)"
"COURTESY_CARD_VERIFIER_API_AUTH_KEY" = "${local.secret_prefix}courtesy-card-verifier-api-auth-key)"
"COURTESY_CARD_VERIFIER_JWE_CEK_ENC" = "${local.secret_prefix}courtesy-card-verifier-jwe-cek-enc)"
"COURTESY_CARD_VERIFIER_JWE_ENCRYPTION_ALG" = "${local.secret_prefix}courtesy-card-verifier-jwe-encryption-alg)"
"COURTESY_CARD_VERIFIER_JWS_SIGNING_ALG" = "${local.secret_prefix}courtesy-card-verifier-jws-signing-alg)"
"SACRT_OAUTH_VERIFIER_NAME" = "${local.secret_prefix}sacrt-oauth-verifier-name)"
"PAYMENT_PROCESSOR_NAME" = "${local.secret_prefix}payment-processor-name)"
"PAYMENT_PROCESSOR_API_BASE_URL" = "${local.secret_prefix}payment-processor-api-base-url)"
"PAYMENT_PROCESSOR_API_ACCESS_TOKEN_ENDPOINT" = "${local.secret_prefix}payment-processor-api-access-token-endpoint)"
"PAYMENT_PROCESSOR_API_ACCESS_TOKEN_REQUEST_KEY" = "${local.secret_prefix}payment-processor-api-access-token-request-key)"
"PAYMENT_PROCESSOR_API_ACCESS_TOKEN_REQUEST_VAL" = "${local.secret_prefix}payment-processor-api-access-token-request-val)"
"PAYMENT_PROCESSOR_CARD_TOKENIZE_URL" = "${local.secret_prefix}payment-processor-card-tokenize-url)"
"PAYMENT_PROCESSOR_CARD_TOKENIZE_FUNC" = "${local.secret_prefix}payment-processor-card-tokenize-func)"
"PAYMENT_PROCESSOR_CARD_TOKENIZE_ENV" = "${local.secret_prefix}payment-processor-card-tokenize-env)"
"MST_AGENCY_SHORT_NAME" = "${local.secret_prefix}mst-agency-short-name)"
"MST_AGENCY_LONG_NAME" = "${local.secret_prefix}mst-agency-long-name)"
"MST_AGENCY_JWS_SIGNING_ALG" = "${local.secret_prefix}mst-agency-jws-signing-alg)"
"SACRT_AGENCY_SHORT_NAME" = "${local.secret_prefix}sacrt-agency-short-name)"
"SACRT_AGENCY_LONG_NAME" = "${local.secret_prefix}sacrt-agency-long-name)"
"SACRT_AGENCY_MERCHANT_ID" = "${local.secret_prefix}sacrt-agency-merchant-id)"
"SACRT_AGENCY_JWS_SIGNING_ALG" = "${local.secret_prefix}sacrt-agency-jws-signing-alg)"
}

lifecycle {
prevent_destroy = true
# app_settings are managed manually through the portal since they contain secrets
ignore_changes = [app_settings, sticky_settings, tags]
ignore_changes = [tags]
}
}

Expand Down
3 changes: 3 additions & 0 deletions terraform/environment.tf
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
locals {
is_prod = terraform.workspace == "default"
is_test = terraform.workspace == "test"
is_dev = !(local.is_prod || local.is_test)
env_name = local.is_prod ? "prod" : terraform.workspace
env_letter = upper(substr(local.env_name, 0, 1))
subscription_letter = local.is_prod ? "P" : "D"
hostname = local.is_prod ? "benefits.calitp.org" : "${local.env_name}-benefits.calitp.org"
secret_prefix = "@Microsoft.KeyVault(VaultName=KV-CDT-PUB-CALITP-${local.env_letter}-001;SecretName="
}

data "azurerm_resource_group" "main" {
Expand Down
11 changes: 11 additions & 0 deletions terraform/secrets/read.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

if [ $# -ne 2 ]; then
echo "Usage: $0 <D|T|P> <secret_name>"
exit 1
fi

env=$1
secret_name=$2

az keyvault secret show --vault-name "KV-CDT-PUB-CALITP-$env-001" --name "$secret_name"

0 comments on commit 5d1fa35

Please sign in to comment.