| @@ -1,16 +1,23 @@ |
| 1 | package e2e |
1 | package e2e |
| 2 | |
2 | |
| 3 | import ( |
3 | import ( |
| |
4 | "bytes" |
| 4 | "fmt" |
5 | "fmt" |
| 5 | "net" |
6 | "net" |
| 6 | "os" |
7 | "os" |
| 7 | "os/exec" |
8 | "os/exec" |
| 8 | "path/filepath" |
9 | "path/filepath" |
| |
10 | "regexp" |
| 9 | "strings" |
11 | "strings" |
| 10 | "testing" |
12 | "testing" |
| 11 | "time" |
13 | "time" |
| 12 | ) |
14 | ) |
| 13 | |
15 | |
| |
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". |
| |
19 | var secretsCheckOneSealed = regexp.MustCompile(`build_secrets\.value: key \S+ 1`) |
| |
20 | |
| 14 | func TestAdminBackup(t *testing.T) { |
21 | func TestAdminBackup(t *testing.T) { |
| 15 | t.Parallel() |
22 | t.Parallel() |
| 16 | inst := startInstance(t) |
23 | inst := startInstance(t) |
| @@ -35,6 +42,11 @@ func TestAdminBackup(t *testing.T) { |
| 35 | if _, _, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/keep", "--title", "'survives backup'"); code != 0 { |
42 | if _, _, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/keep", "--title", "'survives backup'"); code != 0 { |
| 36 | t.Fatal("issue create failed") |
43 | t.Fatal("issue create failed") |
| 37 | } |
44 | } |
| |
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 | } |
| 38 | |
50 | |
| 39 | // Back up while the daemon is running. |
51 | // Back up while the daemon is running. |
| 40 | archive := filepath.Join(t.TempDir(), "backup.tar.gz") |
52 | archive := filepath.Join(t.TempDir(), "backup.tar.gz") |
| @@ -64,6 +76,16 @@ func TestAdminBackup(t *testing.T) { |
| 64 | } |
76 | } |
| 65 | } |
77 | } |
| 66 | } |
78 | } |
| |
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 | } |
| 67 | |
89 | |
| 68 | // Restore: extract into a fresh root and serve from it. |
90 | // Restore: extract into a fresh root and serve from it. |
| 69 | root2 := t.TempDir() |
91 | root2 := t.TempDir() |
| @@ -77,12 +99,13 @@ func TestAdminBackup(t *testing.T) { |
| 77 | [server] |
99 | [server] |
| 78 | root = %q |
100 | root = %q |
| 79 | site_url = "https://gitbay.test" |
101 | site_url = "https://gitbay.test" |
| |
102 | secret_key_file = %q |
| 80 | [ssh] |
103 | [ssh] |
| 81 | port = %d |
104 | port = %d |
| 82 | [http] |
105 | [http] |
| 83 | addr = "127.0.0.1:%d" |
106 | addr = "127.0.0.1:%d" |
| 84 | tls = "off" |
107 | tls = "off" |
| 85 | `, root2, port2, httpPort2) |
108 | `, root2, inst.keyFile, port2, httpPort2) |
| 86 | if err := os.WriteFile(config2, []byte(cfg), 0o600); err != nil { |
109 | if err := os.WriteFile(config2, []byte(cfg), 0o600); err != nil { |
| 87 | t.Fatal(err) |
110 | t.Fatal(err) |
| 88 | } |
111 | } |
| @@ -149,6 +172,23 @@ tls = "off" |
| 149 | if code != 0 || strings.TrimSpace(out2) != "alice" { |
172 | if code != 0 || strings.TrimSpace(out2) != "alice" { |
| 150 | t.Fatalf("whoami on restored instance: exit %d, %q, %s", code, out2, errOut) |
173 | t.Fatalf("whoami on restored instance: exit %d, %q, %s", code, out2, errOut) |
| 151 | } |
174 | } |
| |
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 | } |
| 152 | if out2, _, code = ssh2("repo", "log", "alice/keep"); code != 0 || !strings.Contains(out2, "keep me") { |
192 | if out2, _, code = ssh2("repo", "log", "alice/keep"); code != 0 || !strings.Contains(out2, "keep me") { |
| 153 | t.Fatalf("restored log: %d\n%s", code, out2) |
193 | t.Fatalf("restored log: %d\n%s", code, out2) |
| 154 | } |
194 | } |