cmd/gitbayd/secrets_test.go

ff759b53942049b2043e132f5482b442a7265b98
gitbay/cmd/gitbayd/secrets_test.go history · blame · raw

176 lines · 5108 bytes

  1package main
  2
  3import (
  4	"bytes"
  5	"encoding/base64"
  6	"os"
  7	"path/filepath"
  8	"strings"
  9	"testing"
 10
 11	"gitbay.org/gitbay/internal/config"
 12	"gitbay.org/gitbay/internal/seal"
 13	"gitbay.org/gitbay/internal/store"
 14)
 15
 16func TestOpenStoreRefusesWithoutKeyFile(t *testing.T) {
 17	cfg := testConfig(t)
 18	cfg.Server.SecretKeyFile = filepath.Join(t.TempDir(), "absent.key")
 19	_, err := openStore(cfg)
 20	if err == nil || !strings.Contains(err.Error(), "gitbayd admin secrets init") || !strings.Contains(err.Error(), cfg.Server.SecretKeyFile) {
 21		t.Fatalf("openStore without a key file: %v", err)
 22	}
 23}
 24
 25// storeWithSecret opens cfg's store and stores one build secret.
 26func storeWithSecret(t *testing.T, cfg config.Config) (*store.Store, int64) {
 27	t.Helper()
 28	st, err := openStore(cfg)
 29	if err != nil {
 30		t.Fatal(err)
 31	}
 32	uid, err := st.CreateUser("alice", false)
 33	if err != nil {
 34		t.Fatal(err)
 35	}
 36	repoID, err := st.CreateRepo("user", uid, "app", "public")
 37	if err != nil {
 38		t.Fatal(err)
 39	}
 40	if err := st.SetBuildSecret(repoID, "TOKEN", "v1"); err != nil {
 41		t.Fatal(err)
 42	}
 43	return st, repoID
 44}
 45
 46func TestRotateSecrets(t *testing.T) {
 47	cfg := testConfig(t)
 48	before, err := seal.ReadKeys(cfg.Server.SecretKeyFile)
 49	if err != nil {
 50		t.Fatal(err)
 51	}
 52	st, repoID := storeWithSecret(t, cfg)
 53	st.Close()
 54
 55	var out bytes.Buffer
 56	if err := rotateSecrets(cfg, &out); err != nil {
 57		t.Fatalf("rotate: %v", err)
 58	}
 59	after, err := seal.ReadKeys(cfg.Server.SecretKeyFile)
 60	if err != nil {
 61		t.Fatal(err)
 62	}
 63	if len(after) != 1 || after[0].ID == before[0].ID {
 64		t.Fatalf("key file after rotation holds %v, before %v", after, before)
 65	}
 66	assertNoKeyMaterial(t, out.String(), append(before, after...))
 67	st, err = openStore(cfg)
 68	if err != nil {
 69		t.Fatal(err)
 70	}
 71	defer st.Close()
 72	use, err := st.SecretKeyUse()
 73	if err != nil || use[after[0].ID] != 1 || len(use) != 1 {
 74		t.Fatalf("SecretKeyUse after rotation = %v, %v", use, err)
 75	}
 76	if got, _ := st.BuildSecrets(repoID); got["TOKEN"] != "v1" {
 77		t.Fatalf("value after rotation: %v", got)
 78	}
 79}
 80
 81// A reseal that fails leaves the old keys in the file, so every value
 82// still opens.
 83func TestRotateSecretsKeepsOldKeysWhenResealFails(t *testing.T) {
 84	cfg := testConfig(t)
 85	before, err := seal.ReadKeys(cfg.Server.SecretKeyFile)
 86	if err != nil {
 87		t.Fatal(err)
 88	}
 89	st, repoID := storeWithSecret(t, cfg)
 90	// A second value sealed under a key the file does not hold.
 91	if _, err := st.DB.Exec("INSERT INTO build_secrets (repo_id, name, value) VALUES (?, 'BAD', 'gbs1:deadbeef:AAAA')", repoID); err != nil {
 92		t.Fatal(err)
 93	}
 94	st.Close()
 95
 96	if err := rotateSecrets(cfg, &bytes.Buffer{}); err == nil {
 97		t.Fatal("rotate succeeded over a value that does not open")
 98	}
 99	after, err := seal.ReadKeys(cfg.Server.SecretKeyFile)
100	if err != nil {
101		t.Fatal(err)
102	}
103	if len(after) != 2 || after[0].ID != before[0].ID {
104		t.Fatalf("key file after a failed rotation holds %v", after)
105	}
106}
107
108func TestInitSecrets(t *testing.T) {
109	cfg := testConfig(t)
110	cfg.Server.SecretKeyFile = filepath.Join(t.TempDir(), "secret.key")
111	var out bytes.Buffer
112	if err := initSecrets(cfg, &out); err != nil {
113		t.Fatal(err)
114	}
115	fi, err := os.Stat(cfg.Server.SecretKeyFile)
116	if err != nil {
117		t.Fatal(err)
118	}
119	if fi.Mode().Perm() != 0o600 {
120		t.Fatalf("mode %04o", fi.Mode().Perm())
121	}
122	keys, err := seal.ReadKeys(cfg.Server.SecretKeyFile)
123	if err != nil || len(keys) != 1 {
124		t.Fatalf("keys %v, %v", keys, err)
125	}
126	if !strings.Contains(out.String(), keys[0].ID) {
127		t.Fatalf("output does not name the key id: %q", out.String())
128	}
129	assertNoKeyMaterial(t, out.String(), keys)
130	if err := initSecrets(cfg, &out); err == nil || !strings.Contains(err.Error(), "already exists") {
131		t.Fatalf("second init: %v", err)
132	}
133	if again, _ := seal.ReadKeys(cfg.Server.SecretKeyFile); again[0].ID != keys[0].ID {
134		t.Fatal("second init replaced the key")
135	}
136}
137
138func TestCheckSecrets(t *testing.T) {
139	cfg := testConfig(t)
140	keys, err := seal.ReadKeys(cfg.Server.SecretKeyFile)
141	if err != nil {
142		t.Fatal(err)
143	}
144	st, repoID := storeWithSecret(t, cfg)
145
146	var out bytes.Buffer
147	if err := checkSecrets(cfg, &out); err != nil {
148		t.Fatalf("check: %v\n%s", err, out.String())
149	}
150	if !strings.Contains(out.String(), "build_secrets.value: key "+keys[0].ID+" 1") {
151		t.Fatalf("check output:\n%s", out.String())
152	}
153	assertNoKeyMaterial(t, out.String(), keys)
154
155	if _, err := st.DB.Exec("INSERT INTO build_secrets (repo_id, name, value) VALUES (?, 'BAD', 'gbs1:deadbeef:AAAA')", repoID); err != nil {
156		t.Fatal(err)
157	}
158	st.Close()
159	out.Reset()
160	err = checkSecrets(cfg, &out)
161	if err == nil || !strings.Contains(err.Error(), "does not open 1 stored value") {
162		t.Fatalf("check over a value that does not open: %v", err)
163	}
164	if !strings.Contains(out.String(), "deadbeef") || !strings.Contains(out.String(), "build_secrets.value row") {
165		t.Fatalf("check output does not name the failing row:\n%s", out.String())
166	}
167}
168
169func assertNoKeyMaterial(t *testing.T, out string, keys []seal.Key) {
170	t.Helper()
171	for _, k := range keys {
172		if strings.Contains(out, base64.StdEncoding.EncodeToString(k.Secret)) {
173			t.Fatalf("output carries key %s's secret", k.ID)
174		}
175	}
176}