keys-en-rel

0b7ab92 · gmorell · 2026-05-22 04:05

3 files +54 -24

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