| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "runtime" |
| 9 | "testing" |
| 10 | |
| 11 | "github.com/carlosframework/platform/internal/registry" |
| 12 | "github.com/carlosframework/platform/internal/release" |
| 13 | ) |
| 14 | |
| 15 | func runtimePlatform() string { return runtime.GOOS + "-" + runtime.GOARCH } |
| 16 | |
| 17 | // Astra I2 #1: the agent cannot persist its mark; the next pass, built the |
| 18 | // way production builds it, still refuses the digestless replay. |
| 19 | func TestOneRatchetPerProcessSurvivesAFailedPersist(t *testing.T) { |
| 20 | if os.Geteuid() == 0 { |
| 21 | t.Skip("root can write a 0555 directory") |
| 22 | } |
| 23 | ctx := context.Background() |
| 24 | opt, data := t.TempDir(), t.TempDir() |
| 25 | if err := os.MkdirAll(release.EdgeRatchetDir(data), 0o555); err != nil { |
| 26 | t.Fatal(err) |
| 27 | } |
| 28 | store := release.NewDirStore(t.TempDir()) |
| 29 | seed, pubB64, _ := release.GenerateKey() |
| 30 | priv, _ := release.ParseSigningKey(seed, "t") |
| 31 | pub, _ := release.ParsePublicKey(pubB64, "t") |
| 32 | ship := func(v string) { |
| 33 | m := release.Manifest{Schema: 1, App: "km", Version: v, Kind: "binary", |
| 34 | Artifacts: map[string]release.Artifact{runtimePlatform(): {}}} |
| 35 | if err := release.Ship(ctx, store, "bab", m, map[string][]byte{runtimePlatform(): []byte(v)}); err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | } |
| 39 | ship("v1") |
| 40 | if err := release.Promote(ctx, store, priv, "bab", "km", "canary/x", "v1"); err != nil { // legacy pointer |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | legacy, _, _ := store.Get(ctx, release.ChannelKey("bab", "km", "canary/x")) |
| 44 | if err := release.WriteDigestMarker(ctx, store, release.RootSigner(priv), "bab", "t"); err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | ship("v2") |
| 48 | if err := release.Promote(ctx, store, priv, "bab", "km", "canary/x", "v2"); err != nil { // with digests |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | reg, err := registry.Open(filepath.Join(t.TempDir(), "r.db")) |
| 52 | if err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | if err := reg.Upsert(registry.Route{Host: "a.example.com", Kind: "instance", App: "km", Account: "bab", Channel: "canary/x"}); err != nil { |
| 56 | t.Fatal(err) |
| 57 | } |
| 58 | off := func(string) string { return "" } |
| 59 | pass := func() { |
| 60 | opts := []release.PassOption{release.WithDigestRatchet(pointerRatchet(false, opt, data, off))} // the ratchet fastPassOpts arms, looked up fresh each pass |
| 61 | if err := release.PassFilter(ctx, reg, store, pub, opt, nil, nil, opts...); err != nil { |
| 62 | t.Fatal(err) |
| 63 | } |
| 64 | } |
| 65 | pass() // the digest pointer: its mark cannot be written, so it is refused |
| 66 | if v, _ := release.LiveVersion(opt, "a.example.com"); v != "" { |
| 67 | t.Fatalf("installed %q on a pointer whose mark was not durable", v) |
| 68 | } |
| 69 | _, etag, _ := store.Get(ctx, release.ChannelKey("bab", "km", "canary/x")) |
| 70 | if _, err := store.Put(ctx, release.ChannelKey("bab", "km", "canary/x"), legacy, etag); err != nil { |
| 71 | t.Fatal(err) |
| 72 | } |
| 73 | pass() // the replay, on a new pass |
| 74 | if v, _ := release.LiveVersion(opt, "a.example.com"); v != "" { |
| 75 | t.Fatalf("a later pass in the same process installed the digestless replay %q", v) |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | func TestPointerRatchetSharesMarksBetweenRootAndTheAgent(t *testing.T) { |
| 80 | opt, data := t.TempDir(), t.TempDir() |
| 81 | off := func(string) string { return "" } |
| 82 | p := &release.ChannelPointer{App: "km", Channel: "stable", Version: "v1", Digest: release.ManifestDigest([]byte("m"))} |
| 83 | if err := pointerRatchet(true, opt, data, off).Admit("bab", p); err != nil { |
| 84 | t.Fatal(err) |
| 85 | } |
| 86 | legacy := &release.ChannelPointer{App: "km", Channel: "stable", Version: "v1"} |
| 87 | if err := pointerRatchet(false, opt, data, off).Admit("bab", legacy); !errors.Is(err, release.ErrPointerWithoutDigests) { |
| 88 | t.Fatalf("the agent ignored root's mark: %v", err) |
| 89 | } |
| 90 | on := func(k string) string { |
| 91 | if k == release.RequirePointerDigestEnv { |
| 92 | return "on" |
| 93 | } |
| 94 | return "" |
| 95 | } |
| 96 | fresh := &release.ChannelPointer{App: "km", Channel: "beta", Version: "v1"} |
| 97 | if err := pointerRatchet(false, opt, data, on).Admit("bab", fresh); !errors.Is(err, release.ErrPointerWithoutDigests) { |
| 98 | t.Fatalf("the flag was not read: %v", err) |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // fastPassBox is a box whose fast sweep is built by fastPassOpts, the |
| 103 | // production builder: CARLOS_OPT_DIR points release.OptDir() at a temp dir, |
| 104 | // and route a.example.com follows bab/km on canary/x. |
| 105 | type fastPassBox struct { |
| 106 | store release.Store |
| 107 | priv []byte |
| 108 | opt string |
| 109 | data string |
| 110 | reg *registry.Registry |
| 111 | pub []byte |
| 112 | } |
| 113 | |
| 114 | func newFastPassBox(t *testing.T) *fastPassBox { |
| 115 | t.Helper() |
| 116 | b := &fastPassBox{store: release.NewDirStore(t.TempDir()), opt: t.TempDir(), data: t.TempDir()} |
| 117 | t.Setenv("CARLOS_OPT_DIR", b.opt) |
| 118 | seed, pubB64, _ := release.GenerateKey() |
| 119 | priv, _ := release.ParseSigningKey(seed, "t") |
| 120 | pub, _ := release.ParsePublicKey(pubB64, "t") |
| 121 | b.priv, b.pub = priv, pub |
| 122 | reg, err := registry.Open(filepath.Join(t.TempDir(), "r.db")) |
| 123 | if err != nil { |
| 124 | t.Fatal(err) |
| 125 | } |
| 126 | if err := reg.Upsert(registry.Route{Host: "a.example.com", Kind: "instance", App: "km", Account: "bab", Channel: "canary/x"}); err != nil { |
| 127 | t.Fatal(err) |
| 128 | } |
| 129 | b.reg = reg |
| 130 | return b |
| 131 | } |
| 132 | |
| 133 | func (b *fastPassBox) ship(t *testing.T, v string) { |
| 134 | t.Helper() |
| 135 | m := release.Manifest{Schema: 1, App: "km", Version: v, Kind: "binary", |
| 136 | Artifacts: map[string]release.Artifact{runtimePlatform(): {}}} |
| 137 | if err := release.Ship(context.Background(), b.store, "bab", m, map[string][]byte{runtimePlatform(): []byte(v)}); err != nil { |
| 138 | t.Fatal(err) |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | func (b *fastPassBox) promote(t *testing.T, v string) { |
| 143 | t.Helper() |
| 144 | if err := release.Promote(context.Background(), b.store, b.priv, "bab", "km", "canary/x", v); err != nil { |
| 145 | t.Fatal(err) |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | // pass runs one fast sweep with fastPassOpts' options, exactly as the agent |
| 150 | // builds them each tick. |
| 151 | func (b *fastPassBox) pass(t *testing.T) string { |
| 152 | t.Helper() |
| 153 | opts := fastPassOpts(b.data, func(string, string, error) {}, nil, nil) |
| 154 | if err := release.PassFilter(context.Background(), b.reg, b.store, b.pub, b.opt, nil, nil, opts...); err != nil { |
| 155 | t.Fatal(err) |
| 156 | } |
| 157 | v, _ := release.LiveVersion(b.opt, "a.example.com") |
| 158 | return v |
| 159 | } |
| 160 | |
| 161 | // Astra r1 #6: the ratchet armed by the production builder. A pointer with |
| 162 | // digests is adopted and marks the channel; replaying the saved legacy |
| 163 | // pointer is then refused, and the box keeps serving the digest build. |
| 164 | // Removing WithDigestRatchet from fastPassOpts fails this test. |
| 165 | func TestFastPassOptsRefuseAReplayedLegacyPointer(t *testing.T) { |
| 166 | b := newFastPassBox(t) |
| 167 | ctx := context.Background() |
| 168 | b.ship(t, "v1") |
| 169 | b.promote(t, "v1") // legacy: no marker yet |
| 170 | legacy, _, err := b.store.Get(ctx, release.ChannelKey("bab", "km", "canary/x")) |
| 171 | if err != nil { |
| 172 | t.Fatal(err) |
| 173 | } |
| 174 | if v := b.pass(t); v != "v1" { |
| 175 | t.Fatalf("control: the legacy pointer on a fresh box installed %q, want v1", v) |
| 176 | } |
| 177 | if err := release.WriteDigestMarker(ctx, b.store, release.RootSigner(b.priv), "bab", "t"); err != nil { |
| 178 | t.Fatal(err) |
| 179 | } |
| 180 | b.ship(t, "v2") |
| 181 | b.promote(t, "v2") // with digests |
| 182 | if v := b.pass(t); v != "v2" { |
| 183 | t.Fatalf("the digest pointer installed %q, want v2", v) |
| 184 | } |
| 185 | _, etag, _ := b.store.Get(ctx, release.ChannelKey("bab", "km", "canary/x")) |
| 186 | if _, err := b.store.Put(ctx, release.ChannelKey("bab", "km", "canary/x"), legacy, etag); err != nil { |
| 187 | t.Fatal(err) |
| 188 | } |
| 189 | if v := b.pass(t); v != "v2" { |
| 190 | t.Fatalf("fastPassOpts let a replayed legacy pointer install %q", v) |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | // CARLOS_REQUIRE_POINTER_DIGEST through the production builder: a fresh box |
| 195 | // with the flag on refuses a legacy pointer for a channel it has never seen, |
| 196 | // and with the flag off (the control) it installs it. |
| 197 | func TestFastPassOptsHonourTheRequireFlagOnAFreshBox(t *testing.T) { |
| 198 | for _, on := range []bool{false, true} { |
| 199 | b := newFastPassBox(t) |
| 200 | if on { |
| 201 | t.Setenv(release.RequirePointerDigestEnv, "1") |
| 202 | } else { |
| 203 | t.Setenv(release.RequirePointerDigestEnv, "") |
| 204 | } |
| 205 | b.ship(t, "v1") |
| 206 | b.promote(t, "v1") |
| 207 | got := b.pass(t) |
| 208 | switch { |
| 209 | case on && got != "": |
| 210 | t.Fatalf("flag on: a fresh box installed the legacy pointer's %q", got) |
| 211 | case !on && got != "v1": |
| 212 | t.Fatalf("control, flag off: installed %q, want v1", got) |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |