Skip to content

Cross-repo/cross-org secret distribution via vault-style SOPS encryption #156

Description

@ineedjet

Problem

This repo already has a working "encrypt for a set of recipients, publish, decrypt on demand" mechanism (vaults/*.ymlencrypt-env action → .sops.env release assets → deploy/vault.py's decrypt_env) - but it's scoped narrowly to one use case: a deploy target's own age key decrypting an app's env during a deploy.

Meanwhile there's a real, recurring problem this same mechanism could solve but currently doesn't: sharing a credential (e.g. a Telegram bot token) across multiple repos/orgs. GitHub's own cross-repo secret sharing (organization secrets) only works within one org and needs a paid tier; there's no native GitHub mechanism at all for sharing a secret across orgs. The alternative is a third-party secrets manager SaaS, which is more infrastructure than this problem deserves. Today the actual workaround is manually re-entering the same credential into every consuming repo's own Secrets UI by hand, every time it's needed or rotated.

This isn't a deploy/Docker Compose problem - it's general env/secret reuse across repo and org boundaries, using nothing but git, GitHub Releases, SOPS, and age.

Proposal

Origin side (one canonical repo holding a secret's real value):

  • Holds the actual secret as a plain GitHub Secret, same as today.
  • Holds a registry of every consumer's public age key (same shape as this repo's existing keys/<name>.pub files, just repurposed: each entry is a consuming repo/org, not a deploy target).
  • On rotation (or on every release, mirroring the existing encrypt-vaults/encrypt job pattern), re-encrypts the secret for every registered recipient's public key and publishes one .sops.env per consumer as a release asset - generalizing encrypt-env from "recipients = this org's own deploy targets" to "recipients = anyone who registered a public key."

Consumer side (any repo, any org):

  • Registers its own public age key with the origin repo (e.g. via a PR adding a .pub file - the direction of key exchange is public keys flowing into the origin; encrypted secrets flow out to consumers, private keys never leave their own repo).
  • Pulls the release asset published for it specifically - already works today unmodified, since deploy/resolve.py's download_ref already supports owner/repo@tag:asset refs against any accessible repo, not just this one.
  • Decrypts it with its own private age key (a GitHub Secret local to the consumer repo) and merges the result into the current job's environment ($GITHUB_ENV) for later steps to use.

Open questions

  • Manifest shape for the recipient registry - one registry per secret, or one shared registry the origin applies to everything it holds?
  • Tooling for a consumer to bootstrap its own age keypair (this session did it by hand with age-keygen; worth scripting/documenting properly if this becomes a real pattern).
  • Package the consumer side as a composite action (.github/actions/) any repo can pull in directly, matching the existing encrypt-env/build-bundle pattern.
  • Whether this repo should dogfood it immediately - notify-telegram-pr.yml/notify-telegram-release.yml still read secrets.TELEGRAM_BOT_TOKEN/secrets.TELEGRAM_CHAT_ID directly today, which is exactly the pain this would remove first.

Related

Came up while setting up notify-telegram-release.yml (#152) and continually re-entering the same Telegram credentials by hand across different secret slots.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🚩Temporary fast-track flag

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions