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 "context"
5 "log"
6 "time"
7
8 "github.com/carlosframework/platform/internal/boxid"
9 "github.com/carlosframework/platform/internal/hostactors"
10 "github.com/carlosframework/platform/internal/hostops"
11)
12
13// The console's automatic enrollment pass (region HA spec 2 §5 step 5,
14// H16, H28): every enrollPassInterval it takes the next step for each box
15// its region's group launched that the roster does not list yet
16// (hostactors.AutoEnroller: attempt, invitation, guard-ready, enrollment).
17// It runs beside claim recovery, on a host-claim console with region HA
18// on, and signs with the root key this console already holds. It confirms
19// each box's group membership itself, with its own read-only Auto Scaling
20// and EC2 describes (consoleMembershipReader), immediately before it
21// invites and before it enrolls. The console is the only writer of the
22// roster and the only membership confirmer, so it is the only place this
23// can run.
24
25// enrollPassInterval is how often the pass runs: a box is invited, and
26// enrolled once its guard-ready report is published, within ten seconds.
27const enrollPassInterval = 10 * time.Second
28
29// enrollPatience is how long the pass keeps an attempt the cloud refused
30// before it begins another (doubling with each refusal).
31const enrollPatience = 2 * time.Minute
32
33// newAutoEnroller is the pass over this console's claim authority (its
34// roster, root signer, report evidence and namespace, and the default
35// pool) and its own membership reader. An error when either is missing.
36func newAutoEnroller(a *hostops.ProductionAuthority, members hostactors.MembershipReader) (*hostactors.AutoEnroller, error) {
37 if a == nil || members == nil {
38 return nil, hostops.ErrContract
39 }
40 certs, err := boxid.EmbeddedCerts()
41 if err != nil {
42 return nil, err
43 }
44 return &hostactors.AutoEnroller{Directory: a.Directory, Signer: a.Signer, Evidence: a.Evidence, Members: members, Certs: certs,
45 Namespace: a.Namespace, Pool: "default", Now: time.Now, Patience: enrollPatience, Logf: log.Printf}, nil
46}
47
48// runEnrollPass runs the pass until ctx ends. Each box's step is logged
49// when it changes, and a failed pass on its first failure and every
50// tenth.
51func runEnrollPass(ctx context.Context, e *hostactors.AutoEnroller, every time.Duration, logf func(string, ...any)) {
52 logf("console: automatic box enrollment armed (every %s)", every)
53 last := map[string]string{}
54 fails := 0
55 for {
56 steps, err := e.EnrollPass(ctx)
57 switch {
58 case err != nil:
59 fails++
60 if fails == 1 || fails%10 == 0 {
61 logf("console: automatic box enrollment (failure #%d): %v", fails, err)
62 }
63 default:
64 fails = 0
65 seen := map[string]bool{}
66 for _, st := range steps {
67 seen[st.Box] = true
68 if last[st.Box] != st.Step {
69 logf("console: automatic box enrollment: %s: %s", st.Box, st.Step)
70 last[st.Box] = st.Step
71 }
72 }
73 for box := range last {
74 if !seen[box] {
75 delete(last, box)
76 }
77 }
78 }
79 select {
80 case <-ctx.Done():
81 return
82 case <-time.After(every):
83 }
84 }
85}
86
87// startEnrollPass starts the pass for this server, or logs why it cannot.
88func (s *Server) startEnrollPass(ctx context.Context) {
89 members, err := consoleMembershipReader(ctx)
90 if err != nil {
91 log.Printf("console: automatic box enrollment: not started: %v", err)
92 return
93 }
94 e, err := newAutoEnroller(s.cfg.ClaimAuthority, members)
95 if err != nil {
96 log.Printf("console: automatic box enrollment: not started: %v", err)
97 return
98 }
99 go runEnrollPass(ctx, e, enrollPassInterval, log.Printf)
100}
101