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