# Infrastructure security controls

What the AWS side of Delegus enforces, as built by `infra/`. This is the
companion to `docs/threat-model-mapping.md`: that document maps spec threats to
protocol and service controls; this one states the controls that live in the
accounts, the network, and the key store. Everything here is Terraform in
`infra/` and can be re-derived from it.

## Account structure

- One AWS Organization. The management account holds only Terraform state,
  the organization CloudTrail, and cost budgets; it runs no workload.
- Two workload accounts, `delegus-dev` and `delegus-prod`, under a `workloads`
  organizational unit. Nothing is shared between them: separate VPCs, keys,
  databases, evidence buckets, and deploy roles. A compromise of dev reaches
  nothing in prod.
- Service control policies on the OU deny, even to an account administrator:
  leaving the organization; stopping or deleting CloudTrail; scheduling
  deletion of, or disabling, a production signing key; deleting a production
  evidence bucket or bypassing its retention (S3 itself refuses to suspend
  versioning on an Object Lock bucket).
- Continuous control evidence in production: AWS Config records every
  resource change and evaluates managed rules, Security Hub scores the
  account against AWS Foundational Security Best Practices, GuardDuty
  watches for threats; high and critical findings page the on-call topic.
  Any lifecycle, alias or policy call on a signing key, and any policy,
  Object Lock, versioning or deletion call on the evidence bucket, pages
  the same topic within seconds whether or not the call was denied, and is
  exported as a published transparency record.
- Operators reach the workload accounts only by assuming a role from the
  management account; the CLI signs in with short-lived console credentials,
  never long-lived access keys. MFA is required on the root user and on the
  administrator user.

## Signing keys

- Receipt and status-list signing keys are Ed25519 keys inside AWS KMS
  (`ECC_NIST_EDWARDS25519`, `SIGN_VERIFY`). The private key material never
  leaves the HSM; the API calls `kms:Sign` per receipt and reads the public key
  once at startup.
- Keys are addressed by purpose and generation (`receipt-1`, `receipt-2`,
  `status-1`). Rotation adds a generation and flips the active one; retired
  generations stay enabled and published in the DID document so every
  receipt ever issued keeps verifying. Nothing deletes a key.
- Only the API task role may sign, and only with the keys of its own
  environment. Development keys are published under `did:web:dev.delegus.ai`,
  production keys under `did:web:delegus.ai`; the two sets never meet.

## Evidence store

- One S3 bucket per environment with Object Lock. Production uses COMPLIANCE
  mode with a seven-year default retention: no principal, including the
  account root, can shorten it or delete a locked version. Development uses
  GOVERNANCE mode with one-day retention so it can be cleaned up.
- Versioned, encrypted with a dedicated rotating KMS key, private, and
  policy-locked: TLS only, and any upload naming a different encryption key is
  refused. The API writes objects addressed by SHA-256 with a conditional PUT
  and never deletes.

## Network and edge

- Public entry is a single Application Load Balancer with an ACM certificate;
  TLS 1.2 and 1.3 only, HTTP redirected to HTTPS. API tasks, the database, and
  Redis live in private subnets with no public addresses; security groups
  allow the database and Redis to accept connections only from the API tasks.
- AWS WAF in front of the load balancer: a per-source-IP rate limit, the AWS
  IP-reputation list, and the known-bad-inputs rule group. Blocked and sampled
  requests are logged.
- The database enforces TLS (`rds.force_ssl`) and the API verifies its
  certificate against the Amazon RDS CA. Redis connections use TLS.

## Secrets and deployments

- The database password is generated and rotated by RDS and read from Secrets
  Manager at task start. The admin token is generated at apply time as an
  ephemeral value written through a write-only path, so it never appears in
  Terraform state or plan output; rotating it rolls every task.
- Images are built by GitHub Actions and pushed to a private ECR repository
  with scanning on push. The deploy role is assumed through GitHub OIDC, is
  restricted to the repository's `dev` or `prod` environment, and can only
  push the image and roll the service. Production deploys require a reviewer
  on the GitHub environment. There are no long-lived cloud credentials in CI.

## Logging and alerting

- An organization-wide CloudTrail records every API call in every account,
  all regions, with log-file validation, into a private bucket retained for
  400 days.
- The API writes structured JSON logs to CloudWatch. Alarms notify an SNS
  topic on load-balancer errors, absence of healthy targets, tasks below the
  desired count, database and Redis pressure, API startup failures,
  `SERVICE_UNAVAILABLE` denials, and unhandled errors. Monthly cost budgets
  alert at 80% and 100% of a threshold per account.

## What is deliberately not in place yet

- No Terraform apply from CI: applies run from an operator workstation after a
  plan is read. Terraform CI with plan-on-PR and a second approver is planned.
- No AWS Config or Security Hub yet; planned for the production account.
- Single region (us-east-1). A second region is a repeat of the same modules.
