carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "crypto/x509"
6 "github.com/carlosframework/platform/internal/park"
7
8 "github.com/carlosframework/platform/internal/boxid"
9 "github.com/carlosframework/platform/internal/hostclaim"
10 "github.com/carlosframework/platform/internal/hostwrite"
11)
12
13// Actor is the verified projection of the sole control/host-actors.json roster.
14// Roster authenticates that document and its revision before returning a row.
15type Actor struct {
16 FleetHomeRegion string
17 HomeRegion string
18 ControlBucket string
19 Box, Enrollment string
20 Revision uint64
21 Region, Bucket, Namespace string
22 Cloud CloudIdentity
23 Eligible bool
24 Quarantined bool
25 // Binding is hostactors.BoxBinding of this box's row in the roster read
26 // that produced the actor: the digest installed state is stamped with and
27 // compared against. Unlike Revision, the roster's write counter, it moves
28 // only when this box's identity or state or the deployment's binding facts
29 // change (a quarantine, a readmission, a re-enrolment, a floor publish),
30 // never on holds, activations or other boxes' rows.
31 Binding string
32}
33
34// Bound returns the actor with the roster write counter cleared: the part of
35// the actor that names one enrollment under one binding. Persist this, not the
36// actor, wherever a later comparison must survive unrelated roster writes.
37func (a Actor) Bound() Actor {
38 a.Revision = 0
39 return a
40}
41
42// SameBinding reports whether two actors are the same enrollment under the
43// same binding. Every field but Revision must match; Binding carries the
44// quarantine revision and every other bound fact, so a box quarantined and
45// readmitted since a is never SameBinding with it.
46func SameBinding(a, b Actor) bool {
47 return a.Bound() == b.Bound()
48}
49
50type CloudIdentity struct{ Provider, Account, Region, InstanceID string }
51type Roster interface {
52 Actor(context.Context, string) (Actor, error)
53}
54
55// BoxEvidence loads the exact bytes stored at boxid.Key(box).
56type BoxEvidence interface {
57 Load(context.Context, string) ([]byte, error)
58}
59
60type IdentityProvider struct {
61 HomeRegion string
62 AWSRegion string
63 Roster Roster
64 Evidence BoxEvidence
65 Account, Box, Region, Bucket, Namespace string
66 Certs map[string]*x509.Certificate
67}
68
69func (p IdentityProvider) Verify(ctx context.Context) (Actor, error) {
70 if p.Roster == nil || p.Evidence == nil || p.Account == "" || p.Box == "" || p.Region == "" || p.AWSRegion == "" || p.Bucket == "" || p.Namespace == "" || len(p.Certs) == 0 {
71 return Actor{}, ErrUnsupported
72 }
73 a, err := p.Roster.Actor(ctx, p.Box)
74 if err != nil {
75 return Actor{}, err
76 }
77 if !p.actorMatches(a) {
78 return Actor{}, ErrChanged
79 }
80 raw, err := p.Evidence.Load(ctx, p.Box)
81 if err != nil {
82 return Actor{}, err
83 }
84 // The roster entry is the expected binding: Enroll refuses a record whose
85 // signed instance is not the one this reference is enrolled as.
86 cloud := boxid.Cloud{Provider: a.Cloud.Provider, Account: a.Cloud.Account, Region: a.Cloud.Region, InstanceID: a.Cloud.InstanceID}
87 ev, err := (boxid.Verifier{Account: p.Account, Certs: p.Certs}).Enroll(raw, boxid.Expect{
88 Box: p.Box,
89 AWSRegion: a.Cloud.Region,
90 Roster: []boxid.Enrolled{{Box: a.Box, Cloud: cloud}},
91 })
92 if err != nil {
93 return Actor{}, err
94 }
95 if ev.Box != p.Box || ev.Cloud != cloud {
96 return Actor{}, ErrChanged
97 }
98 if p.HomeRegion != "" && (p.HomeRegion != a.FleetHomeRegion || p.HomeRegion != a.Region || park.PlacementRegion(p.HomeRegion) != p.HomeRegion) {
99 return Actor{}, ErrChanged
100 }
101 a.HomeRegion = p.HomeRegion
102 return a, nil
103}
104
105func (p IdentityProvider) actorMatches(a Actor) bool {
106 return p.AWSRegion != "" && a.Box == p.Box && a.Enrollment != "" && a.Revision > 0 && a.Binding != "" && a.Eligible && !a.Quarantined && a.Region == p.Region && a.Bucket == p.Bucket && a.Namespace == p.Namespace && a.Cloud.Provider == "aws" && a.Cloud.Account == p.Account && a.Cloud.Region == p.AWSRegion && a.Cloud.InstanceID != ""
107}
108
109// LocalAuthority is Plan 8's nonblocking local-state reader. It checks its own
110// boot-clock deadline, loss latch, controller identity, quarantine and fences.
111// No second execution lease state machine is introduced here.
112type LocalAuthority interface {
113 Check(context.Context, Actor, *hostclaim.Claim, Selection) error
114}
115
116func (p IdentityProvider) Local(authority LocalAuthority) func(context.Context, *hostclaim.Claim, Selection) error {
117 return func(ctx context.Context, c *hostclaim.Claim, s Selection) error {
118 if authority == nil {
119 return ErrUnsupported
120 }
121 actor, err := p.Verify(ctx)
122 if err != nil {
123 return err
124 }
125 if s.Box != actor.Box || s.Region != actor.Region || s.HomeRegion != actor.HomeRegion || s.FleetHomeRegion != actor.FleetHomeRegion {
126 return ErrChanged
127 }
128 return authority.Check(ctx, actor, c, s)
129 }
130}
131
132// Startup evaluates the switch before constructing any authority provider.
133func Startup(ctx context.Context, getenv func(string) string, construct func(context.Context) (hostwrite.Selector, error)) (hostwrite.Config, error) {
134 c := hostwrite.Config{Enabled: hostwrite.Enabled(getenv)}
135 if !c.Enabled {
136 return c, nil
137 }
138 if construct == nil {
139 return c, hostwrite.ErrUnavailable
140 }
141 selector, err := construct(ctx)
142 if err != nil {
143 return c, err
144 }
145 if selector == nil {
146 return c, hostwrite.ErrUnavailable
147 }
148 c.Selector = selector
149 return c, nil
150}
151