diff --git a/docs/docs/designs/aws-lab-account.md b/docs/docs/designs/aws-lab-account.md index 228e757..6feb1e2 100644 --- a/docs/docs/designs/aws-lab-account.md +++ b/docs/docs/designs/aws-lab-account.md @@ -273,8 +273,8 @@ An AWS-resident bootstrap instance must be able to, starting from only its IAM role: 1. Reach the tailnet. -2. Clone the private `secrets/` repo from GitHub. -3. Decrypt SOPS-encrypted files in that repo. +2. Fetch encrypted files from the private `secrets/` repo on GitHub. +3. Decrypt those SOPS-encrypted files locally. At no point may the instance hold a durable, plaintext credential for any of the three systems (Tailscale, GitHub, SOPS). All identity traces back to the @@ -297,22 +297,25 @@ automation paths live in a separate secrets design doc. This document only establishes that the KMS key lives in the `lab` account and is the anchor for machine decryption. -### GitHub App for repo access +### GitHub App token broker -Cloning private repos from an AWS-resident bootstrap instance uses a +Fetching private repo contents from an AWS-resident bootstrap instance uses a **GitHub App** owned by the `GilmanLab` organization and installed on the -`secrets` repo (plus any other private repos bootstrap needs to reach). +`secrets` repo. - The App's **private signing key** is stored in an SSM Parameter Store `SecureString` in the `lab` account. -- The instance's IAM role grants `ssm:GetParameter` + `kms:Decrypt` on that - specific parameter path only. -- On bootstrap, the instance fetches the key, generates a JWT, exchanges it - for a short-lived installation token (1-hour TTL), and clones. +- The `github-token-broker` Lambda execution role grants `ssm:GetParameter` on + the GitHub App parameter path. +- Bootstrap principals get `lambda:InvokeFunction` on the broker, not direct + access to the App private key. +- On bootstrap, the caller invokes the broker, receives a short-lived + installation token for `GilmanLab/secrets` with `contents:read`, and then + fetches encrypted files with `git` or the GitHub Contents API. The only durable non-AWS secret anywhere in the chain is the App's private -signing key itself, and that key is at rest in AWS, gated by IAM. Installation -tokens are never stored on disk. +signing key itself, and that key is at rest in AWS behind the broker execution +role. Installation tokens are never stored on disk. ### The single-anchor property @@ -321,9 +324,10 @@ Taken together, the chain on a single EC2 bootstrap instance is: | Step | Identity used | |------|----------------------------------------------------------------| | Join tailnet | IAM role (via workload identity federation) | -| Read GitHub App key | IAM role (via instance profile → SSM + KMS) | -| Mint installation token | App private key (short-lived, in memory) | -| Clone `secrets/` repo | Installation token (short-lived, in memory) | +| Invoke GitHub token broker | IAM role (via instance profile → Lambda) | +| Read GitHub App key | Lambda execution role (via SSM, optionally KMS) | +| Mint installation token | App private key (short-lived, in broker memory) | +| Fetch encrypted `secrets/` files | Installation token (short-lived, in caller memory) | | Decrypt SOPS files | IAM role (via instance profile → KMS) | Every persistent identity is the IAM role. Lose AWS, lose bootstrap. Gain AWS, diff --git a/docs/docs/designs/secrets-and-pki.md b/docs/docs/designs/secrets-and-pki.md index e43872d..cf34ee3 100644 --- a/docs/docs/designs/secrets-and-pki.md +++ b/docs/docs/designs/secrets-and-pki.md @@ -111,10 +111,6 @@ arn:aws:kms:us-west-2:186067932323:key/2aba1d94-6eaf-4d80-8d26-2077f32fd7c5 The existing PGP and age recipients are removed from SOPS metadata. After the cutover, AWS is the only routine decrypt control surface. -This is an intentional break from the current state. Today, `secrets/.sops.yaml` -contains only age and PGP recipients. The KMS key exists, but the SOPS -recipient rollout has not been performed. - ### Scoped decryption SOPS files are encrypted with AWS KMS encryption context so IAM can grant @@ -154,9 +150,10 @@ Private repository access uses a GitHub App owned by `GilmanLab` and installed on `GilmanLab/secrets`. The App private signing key is stored in SSM Parameter Store as a SecureString -in the `lab` account. A bootstrap workload uses its AWS identity to read that -specific SSM parameter, generates a GitHub App JWT, exchanges it for a -short-lived installation token, and clones the repository over HTTPS. +in the `lab` account. Bootstrap workloads do not read that key directly. They +invoke the `github-token-broker` Lambda with their AWS identity. The broker is +the only normal principal that can read the App private key, generate a GitHub +App JWT, and exchange it for a short-lived installation token. Installation tokens are requested with the narrowest useful shape: @@ -170,6 +167,11 @@ The GitHub token grants access to encrypted files. AWS KMS grants access to plaintext. A workload may be able to clone the whole private repository while still being unable to decrypt files outside its KMS context scope. +Callers may use either `git` or the GitHub Contents API with `curl` to fetch +encrypted files. The no-`git` path still uses the GitHub token only for +encrypted repository access; the Lambda does not return SOPS files and does +not decrypt them. + ### Historical exposure Removing PGP and age recipients from current SOPS files does not remove their