| 1 | //go:build linux && rootrunner |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "crypto/ed25519" |
| 8 | "encoding/base64" |
| 9 | "encoding/json" |
| 10 | "github.com/carlosframework/platform/internal/moves" |
| 11 | "github.com/carlosframework/platform/internal/release" |
| 12 | "github.com/carlosframework/platform/internal/rootstate" |
| 13 | "github.com/carlosframework/platform/internal/seeds" |
| 14 | "os" |
| 15 | "path/filepath" |
| 16 | "testing" |
| 17 | ) |
| 18 | |
| 19 | func TestPlan14RootSeedsFloorRootRunner(t *testing.T) { |
| 20 | for _, prev := range []bool{false, true} { |
| 21 | t.Run(map[bool]string{false: "absent", true: "present"}[prev], func(t *testing.T) { |
| 22 | opt := generationRootRunnerDir(t) |
| 23 | dd := filepath.Join(opt, "data") |
| 24 | if err := os.Mkdir(dd, 0755); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | id := rootFloorIdentity{"deploy", "box", "en"} |
| 28 | pub, key, _ := ed25519.GenerateKey(nil) |
| 29 | t.Setenv("CARLOS_RELEASE_PUBKEY", base64.StdEncoding.EncodeToString(pub)) |
| 30 | t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "deploy") |
| 31 | rootFloorSignRunnerBytes(t, opt, systemBinaryPath(opt), "v2.0.0", []byte("#!/bin/sh\nexit 0\n"), key) |
| 32 | if prev { |
| 33 | rootFloorSignRunnerBytes(t, opt, systemPrevPath(opt), "v2.0.0", []byte("#!/bin/sh\nexit 0 #prev\n"), key) |
| 34 | } |
| 35 | identities, e := rootstate.Open(filepath.Join(opt, "system", "guard", "identities"), false, true) |
| 36 | if e != nil { |
| 37 | t.Fatal(e) |
| 38 | } |
| 39 | names, e := identities.Names() |
| 40 | if e != nil { |
| 41 | t.Fatal(e) |
| 42 | } |
| 43 | for _, name := range names { |
| 44 | raw, e := identities.Read(name) |
| 45 | if e != nil { |
| 46 | t.Fatal(e) |
| 47 | } |
| 48 | var p release.SystemPointer |
| 49 | if release.VerifyRootEnvelope(pub, raw, &p) != nil { |
| 50 | t.Fatal("identity") |
| 51 | } |
| 52 | p.Current.SeedsProtocol = 1 |
| 53 | wire, e := release.SignRootEnvelope(release.RootSigner(key), p) |
| 54 | if e != nil { |
| 55 | t.Fatal(e) |
| 56 | } |
| 57 | if e := identities.Write(name, wire); e != nil { |
| 58 | t.Fatal(e) |
| 59 | } |
| 60 | } |
| 61 | identities.Close() |
| 62 | f, err := openRootProtocolFloors(rootSeedsFloorDir(opt), id, true) |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | defer f.close() |
| 67 | a := moves.Floor{Min: 1, MinRelease: "v1.0.0"} |
| 68 | b := moves.Floor{Min: 1, MinRelease: "v2.0.0"} |
| 69 | if err := f.reconstruct(t.Context(), rootFloorReconstruction{Identity: id, Floor: &a, Source: "root history", Digest: floorTestDigest, Authorization: floorTestAuthorization(t, f)}); err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | j := rootFloorJudge{floors: f, current: systemBinaryPath(opt), prev: systemPrevPath(opt), binary: func(path string) (rootFloorBinary, error) { return readRootSeedsBinary(opt, path) }, idle: func(context.Context) error { return nil }} |
| 73 | if err := j.judge(t.Context()); err != nil { |
| 74 | t.Fatal(err) |
| 75 | } |
| 76 | store := release.NewDirStore(t.TempDir()) |
| 77 | raw, _ := json.Marshal(seeds.Floor{Min: b.Min, MinRelease: b.MinRelease}) |
| 78 | if err := os.MkdirAll(filepath.Join(dd, ".moves"), 0755); err != nil { |
| 79 | t.Fatal(err) |
| 80 | } |
| 81 | if err := os.WriteFile(filepath.Join(dd, seeds.FloorFile), raw, 0644); err != nil { |
| 82 | t.Fatal(err) |
| 83 | } |
| 84 | wire, err := release.SignRootEnvelope(release.RootSigner(key), rootSeedsPublication{Schema: 1, Deployment: id.Deployment, Purpose: seeds.FloorKey, Floor: b}) |
| 85 | if err != nil { |
| 86 | t.Fatal(err) |
| 87 | } |
| 88 | if _, err := store.Put(t.Context(), seeds.FloorKey, wire, ""); err != nil { |
| 89 | t.Fatal(err) |
| 90 | } |
| 91 | initial, err := release.SignRootEnvelope(release.RootSigner(key), rootSeedsPublication{Schema: 1, Deployment: id.Deployment, Purpose: seeds.FloorKey, Floor: a}) |
| 92 | if err != nil { |
| 93 | t.Fatal(err) |
| 94 | } |
| 95 | if err := acceptRootSeedsPublication(t.Context(), f, initial); err != nil { |
| 96 | t.Fatal(err) |
| 97 | } |
| 98 | edge := edgesFile{Box: id.Box, MovesFloorState: "accepted"} |
| 99 | if got := withSeedsFields(edge, dd, opt); got.SeedsFloorMinRelease != a.MinRelease { |
| 100 | t.Fatal("mirror advertised ahead", got) |
| 101 | } |
| 102 | if err := refreshRootSeedsFloor(t.Context(), store, opt, dd, id); err != nil { |
| 103 | t.Fatal(err) |
| 104 | } |
| 105 | if got := withSeedsFields(edge, dd, opt); got.SeedsFloorState == "accepted" { |
| 106 | t.Fatal("stale verdict accepted", got) |
| 107 | } |
| 108 | if err := j.judge(t.Context()); err != nil { |
| 109 | t.Fatal(err) |
| 110 | } |
| 111 | if got := withSeedsFields(edge, dd, opt); got.SeedsFloorState != "accepted" || got.SeedsFloorMinRelease != b.MinRelease { |
| 112 | t.Fatal("no root acceptance", got) |
| 113 | } |
| 114 | if err := os.RemoveAll(dd); err != nil { |
| 115 | t.Fatal(err) |
| 116 | } |
| 117 | aplier := systemApplier{optDir: opt, dataDir: dd, rootMoves: func() (rootFloorIdentity, bool, error) { return id, true, nil }} |
| 118 | if aplier.seedsActivationRefusal("v1.0.0") == nil { |
| 119 | t.Fatal("erased mirror permitted downgrade") |
| 120 | } |
| 121 | if err := aplier.seedsActivationRefusal("v2.0.0"); err != nil { |
| 122 | t.Fatal(err) |
| 123 | } |
| 124 | // The distinct moves namespace cannot inherit a seeds acceptance. |
| 125 | if _, err := readRootFloorProjection(opt); !os.IsNotExist(err) { |
| 126 | t.Fatal("moves namespace created", err) |
| 127 | } |
| 128 | }) |
| 129 | } |
| 130 | } |
| 131 | |