keys-en-rel
0b7ab92 · gmorell · 2026-05-22 04:05
Files changed
modified
lib/git_gud/packages/token.ex
+27
−0
@@ -93,6 +93,33 @@ defmodule GitGud.Packages.Token do
| 93 | 93 | end |
| 94 | 94 | end |
| 95 | 95 | |
| 96 | + @doc """ | |
| 97 | + Generate a fresh RSA keypair for token signing. Returns a tuple of | |
| 98 | + `{private_pem, public_pem}` where: | |
| 99 | + | |
| 100 | + * `private_pem` is PKCS#1 (`-----BEGIN RSA PRIVATE KEY-----`) — | |
| 101 | + what Joken/JOSE accept directly via `Joken.Signer.create/3`. | |
| 102 | + * `public_pem` is SubjectPublicKeyInfo (`-----BEGIN PUBLIC KEY-----`) — | |
| 103 | + what zot's `auth.bearer.cert` parses with ParsePKIXPublicKey. | |
| 104 | + | |
| 105 | + Used by `Mix.Tasks.Gitgud.Registry.Keygen` (dev/local) and | |
| 106 | + `GitGud.Release.keygen/1` (in-pod, no Mix). | |
| 107 | + """ | |
| 108 | + def generate_keypair(bits \\ 4096) when is_integer(bits) and bits >= 2048 do | |
| 109 | + rsa_private = :public_key.generate_key({:rsa, bits, 65_537}) | |
| 110 | + priv_pem = encode_pem(:RSAPrivateKey, rsa_private) | |
| 111 | + | |
| 112 | + rsa_public = {:RSAPublicKey, elem(rsa_private, 2), elem(rsa_private, 3)} | |
| 113 | + pub_pem = encode_pem(:SubjectPublicKeyInfo, rsa_public) | |
| 114 | + | |
| 115 | + {priv_pem, pub_pem} | |
| 116 | + end | |
| 117 | + | |
| 118 | + defp encode_pem(asn1_type, entity) do | |
| 119 | + entry = :public_key.pem_entry_encode(asn1_type, entity) | |
| 120 | + :public_key.pem_encode([entry]) | |
| 121 | + end | |
| 122 | + | |
| 96 | 123 | defp authorize_repo(user, %Repository{} = repo, actions) do |
| 97 | 124 | repo = GitGud.Repo.preload(repo, :owner) |
| 98 | 125 | is_owner = user && user.id == repo.owner_id |
modified
lib/git_gud/release.ex
+26
−0
@@ -18,6 +18,32 @@ defmodule GitGud.Release do
| 18 | 18 | {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) |
| 19 | 19 | end |
| 20 | 20 | |
| 21 | + @doc """ | |
| 22 | + Mint an RS256 keypair for the OCI registry token endpoint and print | |
| 23 | + both PEMs to stdout with `===PRIV===` / `===PUB===` delimiters. | |
| 24 | + | |
| 25 | + Use from inside a running pod when you don't want key material on | |
| 26 | + your laptop: | |
| 27 | + | |
| 28 | + kubectl -n git-gud exec deployment/git-gud -- \\ | |
| 29 | + bin/git_gud eval 'GitGud.Release.keygen()' | |
| 30 | + | |
| 31 | + Pipe the output through `awk` (see operator runbook) to split the | |
| 32 | + two PEMs and feed them directly into `kubectl create secret` / | |
| 33 | + `kubectl patch secret` without ever touching the local filesystem. | |
| 34 | + """ | |
| 35 | + def keygen(bits \\ 4096) do | |
| 36 | + Application.ensure_all_started(:crypto) | |
| 37 | + Application.ensure_all_started(:public_key) | |
| 38 | + | |
| 39 | + {priv_pem, pub_pem} = GitGud.Packages.Token.generate_keypair(bits) | |
| 40 | + | |
| 41 | + IO.write("===PRIV===\n") | |
| 42 | + IO.write(priv_pem) | |
| 43 | + IO.write("===PUB===\n") | |
| 44 | + IO.write(pub_pem) | |
| 45 | + end | |
| 46 | + | |
| 21 | 47 | defp repos do |
| 22 | 48 | Application.fetch_env!(@app, :ecto_repos) |
| 23 | 49 | end |
modified
lib/mix/tasks/gitgud.registry.keygen.ex
+1
−24
@@ -31,16 +31,7 @@ defmodule Mix.Tasks.Gitgud.Registry.Keygen do
| 31 | 31 | Application.ensure_all_started(:crypto) |
| 32 | 32 | Application.ensure_all_started(:public_key) |
| 33 | 33 | |
| 34 | − # PKCS#1 "BEGIN RSA PRIVATE KEY" for the private half (what | |
| 35 | − # `openssl genrsa` produces; Joken/JOSE handle it natively), | |
| 36 | − # SubjectPublicKeyInfo "BEGIN PUBLIC KEY" for the public half | |
| 37 | − # (what zot's bearer-auth `cert:` loader parses with | |
| 38 | − # ParsePKIXPublicKey). | |
| 39 | − rsa_private = :public_key.generate_key({:rsa, bits, 65_537}) | |
| 40 | − priv_pem = encode_pem(:RSAPrivateKey, rsa_private) | |
| 41 | − | |
| 42 | − rsa_public = rsa_public_from_private(rsa_private) | |
| 43 | − pub_pem = encode_pem(:SubjectPublicKeyInfo, rsa_public) | |
| 34 | + {priv_pem, pub_pem} = GitGud.Packages.Token.generate_keypair(bits) | |
| 44 | 35 | |
| 45 | 36 | emit(opts[:out_priv], "private", priv_pem) |
| 46 | 37 | emit(opts[:out_pub], "public", pub_pem) |
@@ -99,18 +90,4 @@ defmodule Mix.Tasks.Gitgud.Registry.Keygen do
| 99 | 90 | File.chmod!(path, if(label == "private", do: 0o600, else: 0o644)) |
| 100 | 91 | Mix.shell().info("Wrote #{label} key to #{path} (mode #{if label == "private", do: "0600", else: "0644"})") |
| 101 | 92 | end |
| 102 | − | |
| 103 | − defp encode_pem(asn1_type, entity) do | |
| 104 | − entry = :public_key.pem_entry_encode(asn1_type, entity) | |
| 105 | − :public_key.pem_encode([entry]) | |
| 106 | − end | |
| 107 | − | |
| 108 | − # `:public_key.generate_key({:rsa, ...})` returns the full | |
| 109 | − # `RSAPrivateKey` record; positions 3 and 4 are modulus + public | |
| 110 | − # exponent, which is all SubjectPublicKeyInfo needs. | |
| 111 | − defp rsa_public_from_private(rsa_private) do | |
| 112 | − modulus = elem(rsa_private, 2) | |
| 113 | − public_exp = elem(rsa_private, 3) | |
| 114 | − {:RSAPublicKey, modulus, public_exp} | |
| 115 | − end | |
| 116 | 93 | end |
Parents: 90f3abb