Files
cc-ci/nix/modules/secrets.nix
autonomic-bot 433ec9de30 refactor(1b): RL5 — consolidate Nix code under nix/ (modules->nix/modules, hosts->nix/hosts)
flake.nix/flake.lock STAY at root so the build ref #cc-ci is unchanged; only flake's internal
configuration.nix path updated. Root-relative refs inside moved modules re-based ../X -> ../../X
(secrets/bridge/dashboard); configuration.nix's ../../modules imports unchanged (both dirs under nix/).
Living docs (README, architecture/install/secrets/enroll) + .drone.yml comment updated to nix/...;
append-only history logs left as-is. DECISIONS.md records RL5 + the deferred-coordinated RL6.

Verified on cc-ci: nixos-rebuild build 'path:#cc-ci' -> toplevel 8i3jcad9 (BYTE-IDENTICAL to the
pre-move build — store derivations are content-addressed on file contents, module .nix not in the
runtime closure); scripts/lint.sh -> lint: PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:19:09 +01:00

59 lines
3.0 KiB
Nix

# sops-nix wiring (D6 infra secrets). cc-ci decrypts secrets at activation using its own
# ed25519 SSH host key as the age identity (no separate key file to manage on the box).
# Encrypted material lives in the repo-root `secrets/` git SUBMODULE (the private `cc-ci-secrets`
# repo, Phase-1c). RL5 put this module under nix/modules/, so the relative path is
# ../../secrets/secrets.yaml. Readable only by the recipients in secrets/.sops.yaml (host key +
# off-box master recovery key).
{ config, ... }:
{
sops = {
defaultSopsFile = ../../secrets/secrets.yaml;
# Decrypt using the host's SSH host key (converted to an age identity by sops-nix).
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
# Phase-1c: also accept a bootstrap age key at a fixed path — THE one out-of-band secret,
# provisioned to the host before the first rebuild. On the canonical cc-ci this holds the
# host-derived age identity (== the sshKeyPaths recipient, no new exposure); on a fresh/cloned
# host (e.g. the throwaway-VM rebuild) it holds the off-box recovery key, so a host whose SSH
# host key is NOT a sops recipient can still decrypt every secret. NOTE: sops-install-secrets
# aborts activation if this file is set but missing, so it must exist before `nixos-rebuild`.
age.keyFile = "/var/lib/sops-nix/key.txt";
# Do not also look for a GPG key.
gnupg.sshKeyPaths = [ ];
secrets = {
# M0 proof secret — confirms the decrypt path works end to end.
test_secret = { };
# M2 Drone (A2 internal secrets). drone_rpc_secret is shared between the swarm-deployed
# Drone server (inserted as the `rpc_secret` swarm secret by scripts/deploy-drone.sh) and
# the host exec runner (read via the env template below). drone_gitea_client_secret is the
# Gitea OAuth app secret, inserted as the server's `client_secret` swarm secret.
drone_rpc_secret = { };
drone_gitea_client_secret = { };
# M3 comment-bridge (A2). Read by modules/bridge.nix's reconcile oneshot, which copies them
# into swarm secrets the bridge container mounts. webhook_hmac is also set on the Gitea webhook.
bridge_webhook_hmac = { };
bridge_drone_token = { };
bridge_gitea_token = { };
# Phase-1c C2: the wildcard TLS cert+key are now sops secrets (in cc-ci-secrets), decrypted at
# activation to /var/lib/ci-certs/live/{fullchain.pem,privkey.pem} — the exact path the traefik
# reconcile (modules/proxy.nix) already reads. Replaces the prior operator-drops-a-cert-file step.
wildcard_cert = {
path = "/var/lib/ci-certs/live/fullchain.pem";
mode = "0444"; # leaf+intermediate chain — not secret
};
wildcard_key = {
path = "/var/lib/ci-certs/live/privkey.pem";
mode = "0400"; # private key — root only
};
};
# EnvironmentFile for the host exec runner: DRONE_RPC_SECRET rendered from the sops secret.
templates."drone-runner.env".content = ''
DRONE_RPC_SECRET=${config.sops.placeholder.drone_rpc_secret}
'';
};
}