Configuration & secrets
Loading model
All configuration is read from os.environ — by config.py (the Flask config object) and, at
import time, by pyb_flask_utils. Values arrive from three places, highest precedence first:
-
Lambda environment variables, set by
template.yamlfrom itsEnvmapping, keyed by theEnvNameparameter (the only valuesamconfig.tomlpasses) — non-secret values only (URLs, log level,ENV_NAME, the secret’s name). -
actionstools/<env>in Secrets Manager, copied intoos.environbysecrets_loader.pyat cold start withsetdefault, so a Lambda environment variable with the same name wins. -
common/crm, the shared CRM secret, read bypyb_flask_utils.authinto its own dictionary (it is not copied toos.environ): the instance-id → token pairs forAuthorization: Token …and a fallbackAUTH_JWT_RSA_PUBLIC_KEY.
secret_id = os.environ.get("APP_SECRET_ID")
if not secret_id or os.environ.get("AWS_SECRETS_MANAGER") == "False":
return
|
|
The service secret
One JSON secret per account, holding only secrets:
{
"SLACK_TOKEN": "xoxb-…",
"SENTRY_DSN": "https://<key>@<org>.ingest.sentry.io/<project>",
"MESSAGING_API_KEY": "<messaging app token for instance 8001>",
"EVENT_LOGGER_API_KEY": "<event logger token for instance 8001>",
"AUTH_JWT_RSA_PUBLIC_KEY": "ssh-rsa AAAA… (CRM JWT public key; required by pyb_flask_utils)"
}
| Env | Secret | Account |
|---|---|---|
prod |
|
625663093496 |
stage |
|
605668041275 |
The functions' IAM roles allow GetSecretValue on exactly this secret and common/crm
(AWSSecretsManagerGetSecretValuePolicy). Every key is also documented in
Settings & secrets reference.
Changing a value
A secret. Update the secret, then force new execution environments — secrets are read once per cold start:
aws secretsmanager put-secret-value --secret-id actionstools/stage \
--secret-string file://actionstools-stage.json --profile dev
# any configuration change recycles the environments, e.g. re-deploy:
AWS_PROFILE=dev make deploy stage=stage
put-secret-value replaces the whole JSON; fetch it first with get-secret-value, edit, and
write it back.
A non-secret value. Edit the environment’s column of the Env mapping in template.yaml
(for a new value, add the key to both prod and stage and read it with
!FindInMap [Env, !Ref EnvName, <Key>]), commit, deploy. Do not add it to parameter_overrides.
A new secret key. Add it to both secrets, read it in config.py with an assert if it is
required, add a dummy value to tests/init.py and a placeholder to .env.example, and
document it in Settings & secrets reference.
Environments
| prod | stage | |
|---|---|---|
Stack |
|
|
|
|
|
|
|
(empty) |
Tables |
|
|
VPC |
|
|
Counters schedule |
on |
off |
Swagger |
at the base URL |
at the base URL |
Gotchas
-
Secrets are cached for the life of an execution environment. After a rotation, old environments keep the old value until they are recycled.
-
AWS_SECRETS_MANAGER=Falsedisables both secrets, includingcommon/crm. -
ENV_NAMEselects table names, not just labels: anything other thanproductionuses the_stagingtables. -
The secret’s name, not its ARN.
APP_SECRET_IDis the name; Secrets Manager ARNs end in a random suffix, which is why IAM policies use<name>-*.