Commit 1481a12712

1481a12712110d6489f4e6485040e10d6fc0e8a7

parent: 24b9cdbca1

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 09:09 UTC

e2e: a key file per instance; the archive carries secrets sealed and no key

Ref #273

Layout: unified · split

e2e/acme_test.go +2 −1
@@ -29,6 +29,7 @@ func TestACMEServe(t *testing.T) {
2929[server]
3030root = %q
3131site_url = "https://gitbay.example"
32secret_key_file = %q
3233[ssh]
3334port = %d
3435[http]
@@ -36,7 +37,7 @@ addr = "127.0.0.1:%d"
3637tls = "acme"
3738acme_email = "noreply@gitbay.example"
3839acme_http_addr = "127.0.0.1:%d"
39`, inst.root, inst.port, httpsPort, acmeHTTPPort)
40`, inst.root, inst.keyFile, inst.port, httpsPort, acmeHTTPPort)
4041 if err := os.WriteFile(inst.config, []byte(cfg), 0o600); err != nil {
4142 t.Fatal(err)
4243 }
e2e/backup_test.go +41 −1
@@ -1,16 +1,23 @@
11package e2e
22
33import (
4 "bytes"
45 "fmt"
56 "net"
67 "os"
78 "os/exec"
89 "path/filepath"
10 "regexp"
911 "strings"
1012 "testing"
1113 "time"
1214)
1315
16// secretsCheckOneSealed matches "admin secrets check" reporting the one
17// build secret set in TestAdminBackup as sealed under some key, e.g.
18// "build_secrets.value: key 98e412e4 1".
19var secretsCheckOneSealed = regexp.MustCompile(`build_secrets\.value: key \S+ 1`)
20
1421func TestAdminBackup(t *testing.T) {
1522 t.Parallel()
1623 inst := startInstance(t)
@@ -35,6 +42,11 @@ func TestAdminBackup(t *testing.T) {
3542 if _, _, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/keep", "--title", "'survives backup'"); code != 0 {
3643 t.Fatal("issue create failed")
3744 }
45 // A build secret, to show the archive carries it sealed and the key
46 // file not at all.
47 if _, errOut, code := inst.ssh(t, aliceKey, "hunter2-at-rest", "repo", "secret", "set", "alice/keep", "DEPLOY_TOKEN"); code != 0 {
48 t.Fatalf("secret set: %s", errOut)
49 }
3850
3951 // Back up while the daemon is running.
4052 archive := filepath.Join(t.TempDir(), "backup.tar.gz")
@@ -64,6 +76,16 @@ func TestAdminBackup(t *testing.T) {
6476 }
6577 }
6678 }
79 if strings.Contains(names, "secret.key") {
80 t.Fatalf("archive carries the key file:\n%s", names)
81 }
82 db, err := exec.Command("tar", "-xzOf", archive, "gitbay.db").Output()
83 if err != nil {
84 t.Fatal(err)
85 }
86 if bytes.Contains(db, []byte("hunter2-at-rest")) {
87 t.Fatal("the archived database carries the build secret in clear")
88 }
6789
6890 // Restore: extract into a fresh root and serve from it.
6991 root2 := t.TempDir()
@@ -77,12 +99,13 @@ func TestAdminBackup(t *testing.T) {
7799[server]
78100root = %q
79101site_url = "https://gitbay.test"
102secret_key_file = %q
80103[ssh]
81104port = %d
82105[http]
83106addr = "127.0.0.1:%d"
84107tls = "off"
85`, root2, port2, httpPort2)
108`, root2, inst.keyFile, port2, httpPort2)
86109 if err := os.WriteFile(config2, []byte(cfg), 0o600); err != nil {
87110 t.Fatal(err)
88111 }
@@ -149,6 +172,23 @@ tls = "off"
149172 if code != 0 || strings.TrimSpace(out2) != "alice" {
150173 t.Fatalf("whoami on restored instance: exit %d, %q, %s", code, out2, errOut)
151174 }
175 // With the original key the restored secrets open; with another key
176 // they do not.
177 if out, err := exec.Command(inst.gitbayd, "--config", config2, "admin", "secrets", "check").CombinedOutput(); err != nil || !secretsCheckOneSealed.Match(out) {
178 t.Fatalf("secrets check on the restored instance: %v\n%s", err, out)
179 }
180 config3 := filepath.Join(root2, "config-wrong-key.toml")
181 wrong := strings.Replace(cfg, fmt.Sprintf("secret_key_file = %q", inst.keyFile),
182 fmt.Sprintf("secret_key_file = %q", filepath.Join(t.TempDir(), "other.key")), 1)
183 if err := os.WriteFile(config3, []byte(wrong), 0o600); err != nil {
184 t.Fatal(err)
185 }
186 if out, err := exec.Command(inst.gitbayd, "--config", config3, "admin", "secrets", "init").CombinedOutput(); err != nil {
187 t.Fatalf("init the wrong key: %v\n%s", err, out)
188 }
189 if out, err := exec.Command(inst.gitbayd, "--config", config3, "admin", "secrets", "check").CombinedOutput(); err == nil || !strings.Contains(string(out), "does not hold") {
190 t.Fatalf("secrets check with the wrong key: %v\n%s", err, out)
191 }
152192 if out2, _, code = ssh2("repo", "log", "alice/keep"); code != 0 || !strings.Contains(out2, "keep me") {
153193 t.Fatalf("restored log: %d\n%s", code, out2)
154194 }
e2e/ssh_test.go +6 −1
@@ -24,6 +24,7 @@ type instance struct {
2424 gitPort int
2525 proc *exec.Cmd
2626 sshDir string // per-user client keys live here
27 keyFile string // server.secret_key_file, outside root
2728}
2829
2930// nextPort hands out candidate ports. Seeded randomly so two test processes
@@ -89,11 +90,13 @@ func startInstanceWith(t *testing.T, extra string) *instance {
8990 gitPort: ports[2],
9091 sshDir: t.TempDir(),
9192 }
93 inst.keyFile = filepath.Join(t.TempDir(), "secret.key")
9294 inst.config = filepath.Join(inst.root, "config.toml")
9395 cfg := fmt.Sprintf(`
9496[server]
9597root = %q
9698site_url = "https://gitbay.test"
99secret_key_file = %q
97100[ssh]
98101port = %d
99102[http]
@@ -102,12 +105,14 @@ tls = "off"
102105[git_daemon]
103106enabled = true
104107port = %d
105`, inst.root, inst.port, inst.httpPort, inst.gitPort)
108`, inst.root, inst.keyFile, inst.port, inst.httpPort, inst.gitPort)
106109 cfg += extra + "\n"
107110 if err := os.WriteFile(inst.config, []byte(cfg), 0o600); err != nil {
108111 t.Fatal(err)
109112 }
110113
114 inst.admin(t, "admin", "secrets", "init")
115
111116 inst.proc = exec.Command(inst.gitbayd, "--config", inst.config, "serve")
112117 inst.proc.Stderr = os.Stderr
113118 if err := inst.proc.Start(); err != nil {
e2e/system_test.go +2 −1
@@ -33,12 +33,13 @@ func TestSystemSSHMode(t *testing.T) {
3333[server]
3434root = %q
3535site_url = "https://gitbay.test"
36secret_key_file = %q
3637[ssh]
3738mode = "system"
3839[http]
3940addr = "127.0.0.1:%d"
4041tls = "off"
41`, inst.root, inst.httpPort)
42`, inst.root, inst.keyFile, inst.httpPort)
4243 if err := os.WriteFile(inst.config, []byte(cfg), 0o600); err != nil {
4344 t.Fatal(err)
4445 }