#!/usr/bin/env python3 """The one place secrets live on an orchestrator host: a sops+age encrypted store. WHY: credentials used to sit in plaintext all over the box — passwords baked into git remote URLs (`https://user:pass@host/...`, which `git remote -v` happily prints), API keys in .env files, a private key at mode 0644. Anything in a repo is one `git add -A` away from a push. So: ONE encrypted file, OUTSIDE every git tree, and a helper every project uses. store: /srv/secrets/store.yaml sops+age ciphertext, mode 0600 age key: ~/.config/sops/age/keys.txt the ONLY plaintext secret on disk, 0600 outside git by construction — /srv/secrets is not a repo and has no remote. USAGE (library): from secrets import get, get_group cookie = get("tangled.cookie") env = get_group("cc_ci_testenv") # dict, e.g. to build an env USAGE (CLI): python3 engine/secrets.py list # group/key names only, never values python3 engine/secrets.py get tangled.cookie # value to stdout (careful in logs) python3 engine/secrets.py materialize # write a runtime file a consumer needs MATERIALIZED FILES: some consumers read a fixed path and cannot be taught otherwise (a systemd EnvironmentFile, an ssh IdentityFile, `nix`'s authKeyFile). Those files still exist on disk at 0600, but the STORE IS THE SOURCE OF TRUTH — `materialize` rewrites them from it. Never edit a materialized file by hand; edit the store (`sops /srv/secrets/store.yaml`) and re-materialize, or the two silently drift. ADDING A SECRET: sops /srv/secrets/store.yaml (opens decrypted in $EDITOR, re-encrypts on save) """ import json, os, subprocess, sys, pathlib STORE = os.environ.get("AO_SECRETS_STORE", "/srv/secrets/store.yaml") AGE_KEY = os.environ.get("SOPS_AGE_KEY_FILE", os.path.expanduser("~/.config/sops/age/keys.txt")) # name -> (path, mode). Files a consumer reads from a fixed location; see MATERIALIZED FILES. MATERIALIZE = { "tangled-session": ("engine/.tangled-session", 0o600), # relative to the project dir "cc-ci-testenv": ("/srv/cc-ci/.testenv", 0o600), } def _load(): """Decrypt the store. Fails loudly: a silent empty dict would look like 'no secrets'.""" if not pathlib.Path(STORE).exists(): sys.exit(f"no secret store at {STORE} — see engine/README.md (Secrets)") env = {**os.environ, "SOPS_AGE_KEY_FILE": AGE_KEY} r = subprocess.run(["sops", "-d", "--output-type", "json", STORE], capture_output=True, text=True, env=env) if r.returncode != 0: sys.exit(f"cannot decrypt {STORE} (age key at {AGE_KEY}?): {r.stderr.strip()[:300]}") return json.loads(r.stdout) def get(dotted, default=None): """get('group.key') -> value. Missing key returns default (None) rather than raising.""" cur = _load() for part in dotted.split("."): if not isinstance(cur, dict) or part not in cur: return default cur = cur[part] return cur def get_group(name): """get_group('cc_ci_testenv') -> dict of that group (empty dict if absent).""" return _load().get(name, {}) def materialize(name, project_dir="."): """Write a runtime file from the store. Returns the path written.""" if name == "tangled-session": cookie = get("tangled.cookie") if not cookie: sys.exit("store has no tangled.cookie") p = pathlib.Path(project_dir) / MATERIALIZE[name][0] body = f"TANGLED_COOKIE={cookie}\n" elif name == "cc-ci-testenv": p = pathlib.Path(MATERIALIZE[name][0]) body = "".join(f"{k}={v}\n" for k, v in get_group("cc_ci_testenv").items()) else: sys.exit(f"unknown materialize target {name!r} — known: {', '.join(MATERIALIZE)}") p.parent.mkdir(parents=True, exist_ok=True) p.write_text(body) p.chmod(MATERIALIZE[name][1]) return str(p) def main(): argv = sys.argv[1:] if not argv or argv[0] in ("-h", "--help"): print(__doc__) return cmd = argv[0] if cmd == "list": for g, v in _load().items(): print(f"{g}: {', '.join(v) if isinstance(v, dict) else ''}") elif cmd == "get": if len(argv) < 2: sys.exit("usage: secrets.py get ") v = get(argv[1]) if v is None: sys.exit(f"no such key: {argv[1]}") print(v if isinstance(v, str) else json.dumps(v)) elif cmd == "materialize": if len(argv) < 2: sys.exit(f"usage: secrets.py materialize <{'|'.join(MATERIALIZE)}>") print("wrote", materialize(argv[1], argv[2] if len(argv) > 2 else ".")) else: sys.exit(f"unknown command {cmd!r} — list | get | materialize") if __name__ == "__main__": main()