feat: self-generate and store the terraform registry signing key
Rather than requiring an operator to create a GPG key and a K8s secret, the registry now provisions itself: on first start artifactapi generates a signing keypair and persists it in a new signing_keys table, so all replicas share one key and there is nothing to set up. TF_SIGNING_KEY_PATH still overrides with a bring-your-own key when set. - signing_keys table + GetSigningKey / InsertSigningKeyIfAbsent (ON CONFLICT DO NOTHING so a replica race converges on one key) - tfsign.Generate, LoadArmored, and LoadOrCreate(store, purpose) - server prefers a configured key file, else LoadOrCreate against the DB - tests: generate/load round-trip, load-or-create generates once then reuses, DB insert idempotency
This commit is contained in:
@@ -2,6 +2,7 @@ package tfsign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -69,6 +70,74 @@ func TestLoadSignAndVerify(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateAndLoadArmored(t *testing.T) {
|
||||
priv, keyID, err := Generate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !regexp.MustCompile(`^[0-9A-F]{16}$`).MatchString(keyID) {
|
||||
t.Errorf("generated key id %q malformed", keyID)
|
||||
}
|
||||
|
||||
s, err := LoadArmored(priv, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.KeyID() != keyID {
|
||||
t.Errorf("loaded key id %q != generated %q", s.KeyID(), keyID)
|
||||
}
|
||||
|
||||
msg := []byte("abc terraform-provider-x_1.0.0_linux_amd64.zip\n")
|
||||
sig, err := s.Sign(msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keyring, _ := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(s.PublicKeyArmor())))
|
||||
if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(msg), bytes.NewReader(sig)); err != nil {
|
||||
t.Errorf("signature did not verify: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// memStore is an in-memory KeyStore that records how many keys it accepted.
|
||||
type memStore struct {
|
||||
armor, keyID string
|
||||
found bool
|
||||
inserts int
|
||||
}
|
||||
|
||||
func (m *memStore) GetSigningKey(_ context.Context, _ string) (string, string, bool, error) {
|
||||
return m.armor, m.keyID, m.found, nil
|
||||
}
|
||||
|
||||
func (m *memStore) InsertSigningKeyIfAbsent(_ context.Context, _, armor, keyID string) error {
|
||||
if !m.found { // ON CONFLICT DO NOTHING
|
||||
m.armor, m.keyID, m.found = armor, keyID, true
|
||||
m.inserts++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestLoadOrCreateGeneratesOnceThenReuses(t *testing.T) {
|
||||
store := &memStore{}
|
||||
|
||||
first, err := LoadOrCreate(context.Background(), store, "terraform-provider")
|
||||
if err != nil || first == nil {
|
||||
t.Fatalf("first LoadOrCreate: signer=%v err=%v", first, err)
|
||||
}
|
||||
|
||||
second, err := LoadOrCreate(context.Background(), store, "terraform-provider")
|
||||
if err != nil || second == nil {
|
||||
t.Fatalf("second LoadOrCreate: signer=%v err=%v", second, err)
|
||||
}
|
||||
|
||||
if store.inserts != 1 {
|
||||
t.Errorf("expected exactly one key generated, got %d", store.inserts)
|
||||
}
|
||||
if first.KeyID() != second.KeyID() {
|
||||
t.Errorf("key id changed between loads: %q vs %q", first.KeyID(), second.KeyID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadEmptyPathDisabled(t *testing.T) {
|
||||
s, err := Load("", "")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user