carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostactors
2
3import (
4 "context"
5 "crypto"
6 "crypto/rand"
7 "crypto/rsa"
8 "crypto/sha256"
9 "crypto/x509"
10 "encoding/base64"
11 "encoding/json"
12 "errors"
13 "reflect"
14 "strings"
15 "testing"
16 "time"
17
18 "github.com/carlosframework/platform/internal/boxid"
19 "github.com/carlosframework/platform/internal/release"
20)
21
22func TestParseRoles(t *testing.T) {
23 for in, want := range map[string]string{"default": RoleDefault, "legacy": RoleLegacy, "platform": RolePlatform, " legacy ": RoleLegacy} {
24 if got, err := ParseBoxRole(in); err != nil || got != want {
25 t.Fatalf("ParseBoxRole(%q) = %q, %v", in, got, err)
26 }
27 }
28 for _, in := range []string{"", "Legacy", "draining", "none"} {
29 if _, err := ParseBoxRole(in); !errors.Is(err, ErrRole) {
30 t.Fatalf("ParseBoxRole(%q) accepted", in)
31 }
32 }
33 if _, err := ParseGroupRole("platform"); !errors.Is(err, ErrRole) {
34 t.Fatal("a group took the platform role")
35 }
36 if got, err := ParseGroupRole("legacy"); err != nil || got != RoleLegacy {
37 t.Fatalf("ParseGroupRole(legacy) = %q, %v", got, err)
38 }
39 if RoleName(RoleDefault) != "default" || RoleName(RolePlatform) != "platform" {
40 t.Fatal("RoleName")
41 }
42}
43
44func TestBoxRoleIsSignedAndPersists(t *testing.T) {
45 ctx := context.Background()
46 d, sg, o := fixture(t)
47 next, err := d.SetBoxRole(ctx, sg, o, "box", RoleLegacy)
48 if err != nil {
49 t.Fatal(err)
50 }
51 read, err := d.Read(ctx)
52 if err != nil || read.Roster.Boxes["box"].Role != RoleLegacy || read.ETag != next.ETag {
53 t.Fatalf("read back: %+v %v", read.Roster.Boxes["box"], err)
54 }
55 // The same role again writes nothing.
56 same, err := d.SetBoxRole(ctx, sg, next, "box", RoleLegacy)
57 if err != nil || same.ETag != next.ETag {
58 t.Fatalf("idempotent set wrote: %v", err)
59 }
60 // A stale observation loses the CAS.
61 if _, err = d.SetBoxRole(ctx, sg, o, "box", RolePlatform); err == nil {
62 t.Fatal("stale roster accepted")
63 }
64 back, err := d.SetBoxRole(ctx, sg, next, "box", RoleDefault)
65 if err != nil || back.Roster.Boxes["box"].Role != RoleDefault {
66 t.Fatalf("back to default: %+v %v", back.Roster.Boxes["box"], err)
67 }
68 if _, err = d.SetBoxRole(ctx, sg, back, "nobody", RoleLegacy); !errors.Is(err, ErrRoster) {
69 t.Fatalf("unknown box: %v", err)
70 }
71 if _, err = d.SetBoxRole(ctx, sg, back, "box", "boss"); !errors.Is(err, ErrRole) {
72 t.Fatalf("unknown role: %v", err)
73 }
74 // A retired box keeps no role.
75 retired := Clone(back.Roster)
76 b := retired.Boxes["box"]
77 b.State = "retired"
78 retired.Boxes["box"] = b
79 gone, err := d.Replace(ctx, sg, back, retired)
80 if err != nil {
81 t.Fatal(err)
82 }
83 if _, err = d.SetBoxRole(ctx, sg, gone, "box", RoleLegacy); !errors.Is(err, ErrRoster) {
84 t.Fatalf("retired box: %v", err)
85 }
86}
87
88func TestRosterRefusesUnknownRolesAndAGroupChange(t *testing.T) {
89 ctx := context.Background()
90 d, sg, o := fixture(t)
91 bad := Clone(o.Roster)
92 b := bad.Boxes["box"]
93 b.Role = "boss"
94 bad.Boxes["box"] = b
95 if _, err := d.Replace(ctx, sg, o, bad); !errors.Is(err, ErrRoster) {
96 t.Fatalf("unknown box role written: %v", err)
97 }
98 moved := Clone(o.Roster)
99 b = moved.Boxes["box"]
100 b.Group = "routers"
101 moved.Boxes["box"] = b
102 if _, err := d.Replace(ctx, sg, o, moved); !errors.Is(err, ErrRoster) {
103 t.Fatalf("group rewritten: %v", err)
104 }
105 noGroup := Clone(o.Roster)
106 noGroup.Regions = map[string]Region{"ie": {Account: "123", AWSRegion: "eu-west-1", Role: RoleLegacy}}
107 if _, err := d.Replace(ctx, sg, o, noGroup); !errors.Is(err, ErrRoster) {
108 t.Fatalf("a role on a region with no group: %v", err)
109 }
110 platformGroup := Clone(o.Roster)
111 platformGroup.Regions = map[string]Region{"ie": {Account: "123", AWSRegion: "eu-west-1", GroupName: "routers", Role: RolePlatform}}
112 if _, err := d.Replace(ctx, sg, o, platformGroup); !errors.Is(err, ErrRoster) {
113 t.Fatalf("a platform group: %v", err)
114 }
115}
116
117// groupFixture is fixture plus a group in ie and two boxes it enrolled.
118func groupFixture(t *testing.T) (Directory, release.Signer, Observed) {
119 t.Helper()
120 ctx := context.Background()
121 d, sg, o := fixture(t)
122 next := Clone(o.Roster)
123 next.Regions = map[string]Region{
124 "ie": {Account: "123", AWSRegion: "eu-west-1", GroupName: "routers", GroupARN: "group-arn", Namespaces: []string{"platform"}, Pools: []string{"default"}},
125 "uk": {Account: "123", AWSRegion: "eu-west-2"},
126 }
127 for i, ref := range []string{"g1", "g2"} {
128 next.Boxes[ref] = Box{Enrollment: strings.Repeat(string(rune('c'+i)), 32), Namespace: "platform", BoxID: ref, Label: ref, Region: "ie", Pool: "default", Cloud: Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-" + ref}, EvidenceSHA256: strings.Repeat("b", 64), State: "active", LossIDs: []string{}, Group: "routers"}
129 }
130 o, err := d.Replace(ctx, sg, o, next)
131 if err != nil {
132 t.Fatal(err)
133 }
134 return d, sg, o
135}
136
137func TestGroupRoleSetsEveryMemberInOneWrite(t *testing.T) {
138 ctx := context.Background()
139 d, sg, o := groupFixture(t)
140 next, set, err := d.SetGroupRole(ctx, sg, o, "ie", RoleLegacy)
141 if err != nil {
142 t.Fatal(err)
143 }
144 if !reflect.DeepEqual(set, []string{"g1", "g2"}) {
145 t.Fatalf("set %v", set)
146 }
147 if next.Roster.Revision != o.Roster.Revision+1 {
148 t.Fatalf("revision %d, want one write", next.Roster.Revision)
149 }
150 r := next.Roster
151 if r.Regions["ie"].Role != RoleLegacy || r.Boxes["g1"].Role != RoleLegacy || r.Boxes["g2"].Role != RoleLegacy {
152 t.Fatalf("roles: %+v %+v %+v", r.Regions["ie"], r.Boxes["g1"], r.Boxes["g2"])
153 }
154 // The operator-enrolled box in the same region is not the group's.
155 if r.Boxes["box"].Role != RoleDefault {
156 t.Fatalf("operator box took the group's role: %+v", r.Boxes["box"])
157 }
158 again, _, err := d.SetGroupRole(ctx, sg, next, "ie", RoleLegacy)
159 if err != nil || again.ETag != next.ETag {
160 t.Fatalf("idempotent group set wrote: %v", err)
161 }
162 back, _, err := d.SetGroupRole(ctx, sg, next, "ie", RoleDefault)
163 if err != nil || back.Roster.Boxes["g1"].Role != RoleDefault || back.Roster.Regions["ie"].Role != RoleDefault {
164 t.Fatalf("back to default: %v", err)
165 }
166 if _, _, err = d.SetGroupRole(ctx, sg, back, "uk", RoleLegacy); !errors.Is(err, ErrRoster) {
167 t.Fatalf("a region with no group: %v", err)
168 }
169 if _, _, err = d.SetGroupRole(ctx, sg, back, "ie", RolePlatform); !errors.Is(err, ErrRole) {
170 t.Fatalf("a platform group: %v", err)
171 }
172}
173
174// Automatic enrollment gives the new box the group's role in the same
175// roster write (A11-1): a replacement for an excluded group is never
176// placeable, even for a moment.
177func TestAutomaticEnrollmentInheritsTheGroupRole(t *testing.T) {
178 for _, role := range []string{RoleDefault, RoleLegacy} {
179 t.Run(RoleName(role), func(t *testing.T) {
180 ctx := context.Background()
181 d, sg, o := fixture(t)
182 key, err := rsa.GenerateKey(rand.Reader, 2048)
183 if err != nil {
184 t.Fatal(err)
185 }
186 doc := []byte(`{"accountId":"123","region":"eu-west-1","instanceId":"i-new"}`)
187 sum := sha256.Sum256(doc)
188 sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:])
189 if err != nil {
190 t.Fatal(err)
191 }
192 raw, _ := json.Marshal(boxid.Record{V: 1, Box: newRef, IdentityDocument: base64.StdEncoding.EncodeToString(doc), IdentitySignature: base64.StdEncoding.EncodeToString(sig)})
193 v := boxid.Verifier{Account: "123", Certs: map[string]*x509.Certificate{"eu-west-1": {PublicKey: &key.PublicKey, PublicKeyAlgorithm: x509.RSA}}}
194 in := EnrollmentRequest{Reference: newRef, Namespace: "platform", Label: newRef, Region: "ie", Pool: "default", AWSRegion: "eu-west-1"}
195 next := Clone(o.Roster)
196 next.Regions = map[string]Region{"ie": {Account: "123", AWSRegion: "eu-west-1", GroupName: "routers", GroupARN: "group-arn", Namespaces: []string{"platform"}, Pools: []string{"default"}, Role: role}}
197 if o, err = d.Replace(ctx, sg, o, next); err != nil {
198 t.Fatal(err)
199 }
200 if _, err = d.Invite(ctx, sg, in.Reference, raw); err != nil {
201 t.Fatal(err)
202 }
203 o, a, err := d.BeginEnrollmentAttempt(ctx, sg, o, in, raw, "i-new")
204 if err != nil {
205 t.Fatal(err)
206 }
207 // The console's own read: a running member of the group.
208 cloud := &fakeMembership{members: map[string]Membership{"i-new": member("routers")}}
209 in.AttemptID, in.ConfirmationID = a.AttemptID, a.ConfirmationID
210 admitted, _, err := d.EnrollAutomatic(ctx, sg, o, v, in, raw, "i-new", cloud, time.Now)
211 if err != nil {
212 t.Fatal(err)
213 }
214 b := admitted.Roster.Boxes[newRef]
215 if b.Group != "routers" || b.Role != role {
216 t.Fatalf("admitted box group %q role %q, want routers %q", b.Group, b.Role, role)
217 }
218 if admitted.Roster.Revision != o.Roster.Revision+1 {
219 t.Fatal("the role was not set in the enrolling write")
220 }
221 })
222 }
223}
224
225// Every other roster write keeps the role: a quarantine, for one, rewrites
226// the box's entry and must not reopen placement on it.
227func TestOtherRosterWritesKeepTheRole(t *testing.T) {
228 ctx := context.Background()
229 d, sg, o := fixture(t)
230 o, err := d.SetBoxRole(ctx, sg, o, "box", RoleLegacy)
231 if err != nil {
232 t.Fatal(err)
233 }
234 q, err := d.Quarantine(ctx, sg, o, "box", o.Roster.Boxes["box"].Enrollment, "loss-1")
235 if err != nil {
236 t.Fatal(err)
237 }
238 if b := q.Roster.Boxes["box"]; b.Role != RoleLegacy || b.State != "quarantined" {
239 t.Fatalf("after a quarantine: %+v", b)
240 }
241 read, err := d.Read(ctx)
242 if err != nil || read.Roster.Boxes["box"].Role != RoleLegacy {
243 t.Fatalf("read back: %+v %v", read.Roster.Boxes["box"], err)
244 }
245}
246