carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "crypto/ed25519"
5 "encoding/json"
6 "errors"
7 "reflect"
8 "runtime"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/hostactors"
14 "github.com/carlosframework/platform/internal/hostfloor"
15 "github.com/carlosframework/platform/internal/release"
16 "github.com/carlosframework/platform/internal/version"
17)
18
19func TestHostConsoleAdmissionAfterFloorPublication(t *testing.T) {
20 t.Setenv("CARLOS_HOST_CLAIM", "1")
21 build := version.Build
22 version.Build = "build123"
23 t.Cleanup(func() { version.Build = build })
24 for _, fault := range []string{"none", "missing", "unsigned", "wrong-signer", "wrong-bucket", "wrong-id", "wrong-enrollment", "wrong-compute", "unready", "stale", "report-enrollment", "guard-error"} {
25 t.Run(fault, func(t *testing.T) {
26 ctx := t.Context()
27 s, d, _ := activationFixture(t)
28 s.cfg.HostConsoleID, s.cfg.ReleasePub, s.cfg.HostClaim = "console-2", d.PublicKey, true
29 signer := release.RootSigner(s.cfg.Key)
30 before, err := d.Read(ctx)
31 if err != nil {
32 t.Fatal(err)
33 }
34 if before.Roster.Publication != "published" {
35 t.Fatal("fixture has no published floor")
36 }
37 c := hostactors.Console{Enrollment: strings.Repeat("c", 32), InstanceID: s.cfg.HostConsoleID, Namespaces: []string{"platform"}, DeploymentBucket: d.Bucket}
38 invited := c
39 bucket := d.Bucket
40 switch fault {
41 case "wrong-bucket":
42 bucket = "other"
43 case "wrong-id":
44 invited.InstanceID = "other"
45 case "wrong-enrollment":
46 invited.Enrollment = "invalid"
47 case "wrong-compute":
48 invited.Compute = &hostactors.ComputeBinding{Box: "unknown", Enrollment: strings.Repeat("a", 32)}
49 }
50 invitation := hostactors.ConsoleInvitation{V: 1, Bucket: bucket, Console: invited}
51 invitationSigner := signer
52 if fault == "wrong-signer" {
53 _, key, err := ed25519.GenerateKey(nil)
54 if err != nil {
55 t.Fatal(err)
56 }
57 invitationSigner = release.RootSigner(key)
58 }
59 wire, err := release.SignRootEnvelope(invitationSigner, invitation)
60 if fault == "unsigned" {
61 wire, err = json.Marshal(invitation)
62 }
63 if err != nil {
64 t.Fatal(err)
65 }
66 if fault != "missing" {
67 if err := release.CreateExact(ctx, d.Store, "control/host-console-invitations/"+c.InstanceID+".json", wire); err != nil {
68 t.Fatal(err)
69 }
70 }
71 platform := runtime.GOOS + "-" + runtime.GOARCH
72 hash := strings.Repeat("d", 64)
73 rel := release.SystemRelease{Version: version.Build, ReleaseTag: "v1.2.3", MovesProtocol: 1, Artifacts: map[string]release.SystemArtifact{platform: {Key: release.SystemArtifactKey(version.Build, platform), SHA256: hash}}, ConsoleArtifacts: map[string]release.SystemArtifact{platform: {Key: "bootstrap/console/build123/carlos-" + platform, SHA256: hash}}}
74 if _, err := release.PromoteSystem(ctx, d.Store, signer, "router", "stable", rel, release.SystemPromoteOptions{}); err != nil {
75 t.Fatal(err)
76 }
77 wire, _, err = d.Store.Get(ctx, release.SystemPointerKey("router", "stable"))
78 if err != nil {
79 t.Fatal(err)
80 }
81 if err := release.PublishSystemIdentity(ctx, d.Store, d.PublicKey, wire); err != nil {
82 t.Fatal(err)
83 }
84 next := hostactors.Clone(before.Roster)
85 next.Consoles[c.InstanceID] = c
86 if _, err := d.Replace(ctx, signer, before, next); err == nil {
87 t.Fatal("invitation admitted console without a report")
88 }
89 called := false
90 report, err := s.hostReport(ctx, func() (hostfloor.Running, error) {
91 called = true
92 if fault == "guard-error" {
93 return hostfloor.Running{}, hostfloor.ErrFloor
94 }
95 return hostfloor.Running{ReleaseTag: rel.ReleaseTag, Hash: hash, Protocol: 1, BootID: "boot", FloorSHA256: before.Roster.FloorSHA256, GuardReady: fault != "unready"}, nil
96 })
97 refused := fault == "missing" || fault == "unsigned" || fault == "wrong-signer" || fault == "wrong-bucket" || fault == "wrong-id" || fault == "wrong-enrollment" || fault == "wrong-compute"
98 if refused || fault == "guard-error" {
99 if err == nil || (refused && called) {
100 t.Fatalf("invalid invitation or guard accepted: called=%v, err=%v", called, err)
101 }
102 return
103 }
104 if err != nil {
105 t.Fatal("invited console cannot report before admission", err)
106 }
107 if report.Actor != c.InstanceID || report.Enrollment != c.Enrollment || report.Component != "console" || report.ProcessID == "" {
108 t.Fatal("incorrect reporting identity", report)
109 }
110 unchanged, err := d.Read(ctx)
111 if err != nil || unchanged.ETag != before.ETag {
112 t.Fatal("reporting changed membership", err)
113 }
114 switch fault {
115 case "stale":
116 report.GeneratedAt = time.Now().Add(-time.Hour)
117 case "report-enrollment":
118 report.Enrollment = strings.Repeat("b", 32)
119 }
120 raw, err := json.Marshal(report)
121 if err != nil {
122 t.Fatal(err)
123 }
124 if err := release.CreateExact(ctx, d.Store, "status/consoles/"+c.InstanceID+".json", raw); err != nil {
125 t.Fatal(err)
126 }
127 admittedAt := time.Now()
128 after, err := d.Replace(ctx, signer, before, next)
129 if fault != "none" {
130 if err == nil {
131 t.Fatal("invalid report admitted console")
132 }
133 return
134 }
135 if err != nil {
136 t.Fatal("admission after reporting failed", err)
137 }
138 enrolled := after.Roster.Consoles[c.InstanceID]
139 if enrolled.EnrolledAt.Before(admittedAt) || enrolled.EnrolledAt.After(time.Now()) {
140 t.Fatal("admission did not assign enrollment time", enrolled.EnrolledAt)
141 }
142 c.EnrolledAt = enrolled.EnrolledAt
143 if !reflect.DeepEqual(enrolled, c) {
144 t.Fatal("admission changed the invited identity", enrolled)
145 }
146 if err := d.Store.Delete(ctx, "control/host-console-invitations/"+c.InstanceID+".json"); err != nil {
147 t.Fatal(err)
148 }
149 _, err = s.hostReport(ctx, func() (hostfloor.Running, error) { return hostfloor.Running{}, hostfloor.ErrFloor })
150 if !errors.Is(err, hostfloor.ErrFloor) {
151 t.Fatal("admitted console still requires invitation", err)
152 }
153 })
154 }
155}
156