Configuration files
Keep 3AM's connections and policy in files, for GitOps or Ansible, instead of setting them in the console. And how to reference secrets.
You never need configuration files: the console sets everything. But if you manage infrastructure as code, you can keep connections and policy in files, review them, and roll them out like anything else.
Put the files in one folder and give it to 3AM, read-only:
- On a server:
./install.sh --config /path/to/folder - On Kubernetes: a ConfigMap, with
config.existingConfigMap. The licence and packs go in Secrets (licence.existingSecret,packs.existingSecret).
| File | What it holds |
|---|---|
connectors.json | The tools 3AM connects to |
policy.json | Autonomy, approval rules, budgets |
estate-overrides.json | Corrections to the services 3AM discovered |
licence.json | The licence |
Anything defined in a file wins over the console. The console shows it as managed in a file, and won't let anyone edit it there.
connectors.json
{
"connectors": [
{"name": "prometheus", "kind": "prometheus",
"settings": {"url": "https://prometheus.example.internal:9090",
"alertmanager_url": "https://alertmanager.example.internal:9093",
"bearer_token": {"file": "/etc/3am/secrets/prometheus/token"}}},
{"name": "pagerduty", "kind": "pagerduty",
"settings": {"api_token": {"env": "PD_API_TOKEN"}, "from_email": "3am@example.com",
"watch_services": ["PXXXXXX"]}}
]
}Each connector's page lists its settings.
Secrets are references
A secret is never written into the file. It points to where the secret is:
| Form | Reads the secret from |
|---|---|
{"file": "/etc/3am/secrets/…"} | A file, such as a mounted Kubernetes Secret |
{"env": "NAME"} | An environment variable of the 3AM container |
3AM refuses to start if it finds a secret written inline, because it would end up in backups, diffs and support bundles.
policy.json
{
"default_autonomy": "shadow",
"approval_chain": [{"channel": "slack"}, {"channel": "pagerduty", "after_s": 300}],
"rules": [
{"match": {"action": "mysql.start"}, "deny": true, "reason": "database restarts go through the DBA team"},
{"match": {"risk": "high"}, "quorum": 2, "hours": "07:00-22:00"}
],
"budgets": {"per_service_per_hour": 3, "global_per_hour": 10}
}| Key | What it does |
|---|---|
default_autonomy | Where new services start: shadow (recommended) or off. Raising a service to L1 or L2 always takes two approvers in the console, never a file |
approval_chain | Where to ask, in order. after_s is how long to wait before moving on to the next channel |
rules | Checked in order; the first match wins. Match on the action, its risk, the service or the environment, then deny it, require more approvers, or limit the hours |
budgets | The most fixes per service per hour, and in total |
The rest of the policy (per-service approvers, business impact, the limits for L2) is explained in Safety rails.
estate-overrides.json
When discovery links something wrongly, correct it here. Overrides always win:
{"services": {"payments": {"repos": ["example/payments-service"], "owner": "ana@example.com", "team": "Payments"}}}The Estate page in the console writes the same corrections.