carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "io"
11 "os"
12 "path/filepath"
13 "runtime"
14 "time"
15
16 "github.com/carlosframework/platform/internal/moves"
17 "github.com/carlosframework/platform/internal/release"
18 "github.com/carlosframework/platform/internal/rootparticipation"
19 "github.com/carlosframework/platform/internal/rootstate"
20)
21
22type rootFloorBinary struct {
23 Version, Digest string
24 Protocol, SeedsProtocol int
25 File rootstate.FileIdentity
26}
27type rootFloorVerdict struct {
28 Identity rootFloorIdentity
29 Generation uint64
30 Current, Prev rootFloorBinary
31 CurrentOK, PrevAbsent, PrevOK bool
32}
33type rootFloorAdvertisement struct {
34 State, MinRelease, PrevRelease, PrevAllowedFor string
35 Generation uint64
36}
37type rootFloorJudge struct {
38 floors rootMovesFloors
39 remote func(context.Context) (*moves.Floor, error)
40 legacy func() ([]*moves.Floor, error)
41 binary func(string) (rootFloorBinary, error)
42 idle func(context.Context) error
43 current, prev string
44}
45
46func (b rootFloorBinary) supports(fl *moves.Floor) bool {
47 if b.Digest == "" || !validFloorRelease(b.Version) {
48 return false
49 }
50 if fl == nil {
51 return true
52 }
53 at, err := moves.ReleaseAtOrAbove(b.Version, fl.MinRelease)
54 return err == nil && at && b.Protocol >= fl.Min
55}
56
57func (j rootFloorJudge) refresh(ctx context.Context) error {
58 remote, rerr := j.remote(ctx)
59 legacy, lerr := j.legacy()
60 if err := j.floors.refresh(ctx, remote, legacy, errors.Join(rerr, lerr)); err != nil {
61 return err
62 }
63 return j.judge(ctx)
64}
65func (j rootFloorJudge) judge(ctx context.Context) error {
66 // Release exclusion is held by the caller. Observe participation before
67 // taking a floor lock; the verifier must never run underneath that lock.
68 idleErr := errRootFloorHistory
69 if j.idle != nil {
70 idleErr = j.idle(ctx)
71 }
72 f := j.floors
73 unlock, err := f.private.Lock(ctx, "floor.lock")
74 if err != nil {
75 return err
76 }
77 defer unlock()
78 p, err := f.projection()
79 if err != nil {
80 bound, err := f.localBound()
81 if err != nil {
82 return err
83 }
84 p = rootFloorProjection{rootFloorRecord: rootFloorRecord{Identity: f.identity, Floor: bound}}
85 }
86 // Revoke a same-generation verdict before any observation that can fail.
87 if err := f.public.Write("prev.json", []byte("{}")); err != nil {
88 return err
89 }
90 v := rootFloorVerdict{Identity: f.identity, Generation: p.Generation}
91 v.Current, err = j.binary(j.current)
92 if err != nil {
93 return err
94 }
95 v.CurrentOK = v.Current.supports(p.Floor)
96 if !v.CurrentOK {
97 rootFloorAlert()
98 }
99 v.Prev, err = j.binary(j.prev)
100 if errors.Is(err, os.ErrNotExist) {
101 v.PrevAbsent = true
102 } else if err != nil {
103 return err
104 } else if v.Prev.supports(p.Floor) {
105 if idleErr != nil {
106 // Retain a negative verdict without blocking the repair executor.
107 v.PrevOK = false
108 } else {
109 v.PrevOK = true
110 }
111 }
112 raw, _ := json.Marshal(v)
113 if err := f.public.Write("prev.json", raw); err != nil {
114 return errors.Join(err, f.public.Write("prev.json", []byte("{}")))
115 }
116 return nil
117}
118func (f rootMovesFloors) advertisement() (rootFloorAdvertisement, error) {
119 p, err := f.projection()
120 if err != nil {
121 return rootFloorAdvertisement{}, err
122 }
123 return rootFloorAdvertisementFor(f.public, p)
124}
125func rootFloorAdvertisementFor(public rootstate.Store, p rootFloorProjection) (rootFloorAdvertisement, error) {
126 ad := rootFloorAdvertisement{State: "quarantined", Generation: p.Generation}
127 raw, err := public.Read("prev.json")
128 if err != nil {
129 return ad, err
130 }
131 var v rootFloorVerdict
132 if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &v) != nil || v.Identity != p.Identity || v.Generation != p.Generation || !v.CurrentOK || !v.Current.supports(p.Floor) {
133 return ad, errRootFloorHistory
134 }
135 ad.State = "accepted"
136 if p.Floor != nil {
137 ad.MinRelease = p.Floor.MinRelease
138 }
139 if v.PrevAbsent {
140 ad.PrevRelease = "none"
141 } else {
142 ad.PrevRelease = v.Prev.Version
143 if v.PrevOK && v.Prev.supports(p.Floor) && p.Floor != nil {
144 ad.PrevAllowedFor = p.Floor.MinRelease
145 }
146 }
147 return ad, nil
148}
149
150// A binary's signed identity, not its mutable version sidecar, supplies its
151// release and protocol. The cache is root-owned; each envelope is reverified.
152func readRootFloorBinary(opt, path string) (rootFloorBinary, error) {
153 return readRootFloorBinaryContext(context.Background(), opt, path)
154}
155
156// readRootFloorBinaryContext is readRootFloorBinary under ctx: the hash of
157// the image stops between reads, and the scan of the identity cache before
158// each identity, once ctx ends. The generation census's service lookup
159// proves signed images with it under the census deadline.
160func readRootFloorBinaryContext(ctx context.Context, opt, path string) (rootFloorBinary, error) {
161 var b rootFloorBinary
162 f, err := rootstate.OpenAuthorityFile(path)
163 if err != nil {
164 return b, err
165 }
166 defer f.Close()
167 b.File, err = rootstate.Identity(f)
168 if err != nil {
169 return b, err
170 }
171 st, err := f.Stat()
172 if err != nil {
173 return b, err
174 }
175 if st.Size() > 512<<20 || st.Mode()&0111 == 0 {
176 return b, os.ErrPermission
177 }
178 sum, err := rootFloorDigest(ctx, f)
179 if err != nil {
180 return b, err
181 }
182 // One single-valued hex write, as before: the moves copy gate derives the
183 // Digest that reaches a seal's Reason from here (approved_moves_copy_test).
184 b.Digest = hex.EncodeToString(sum)
185 pub, err := release.ParsePublicKey(os.Getenv("CARLOS_RELEASE_PUBKEY"), "CARLOS_RELEASE_PUBKEY")
186 if err != nil {
187 return b, err
188 }
189 identities, err := rootstate.Open(filepath.Join(opt, "system", "guard", "identities"), false, true)
190 if err != nil {
191 return b, err
192 }
193 defer identities.Close()
194 names, err := identities.Names()
195 if err != nil {
196 return b, err
197 }
198 return rootFloorSignedIdentity(ctx, pub, b, names, identities.Read)
199}
200
201// rootFloorDigest is the sha256 of an image, read through ctx: the raw sum,
202// which the caller hex-encodes in place.
203func rootFloorDigest(ctx context.Context, image io.Reader) ([]byte, error) {
204 sum := sha256.New()
205 if _, err := io.Copy(sum, generationContextReader{ctx: ctx, r: image}); err != nil {
206 return nil, err
207 }
208 return sum.Sum(nil), nil
209}
210
211// rootFloorSignedIdentity finds the cached signed identity whose router
212// artifact for this platform is b's digest, verifying each envelope, and
213// stops before the next identity once ctx ends.
214func rootFloorSignedIdentity(ctx context.Context, pub ed25519.PublicKey, b rootFloorBinary, names []string, read func(string) ([]byte, error)) (rootFloorBinary, error) {
215 for _, name := range names {
216 if err := ctx.Err(); err != nil {
217 return b, err
218 }
219 raw, err := read(name)
220 if err != nil {
221 return b, err
222 }
223 p, err := release.VerifySystemIdentity(pub, raw)
224 if err != nil {
225 return b, err
226 }
227 if p.Current.Artifacts[runtime.GOOS+"-"+runtime.GOARCH].SHA256 == b.Digest {
228 b.Version, b.Protocol, b.SeedsProtocol = p.Current.ReleaseTag, p.Current.MovesProtocol, p.Current.SeedsProtocol
229 return b, nil
230 }
231 }
232 return b, errRootFloorHistory
233}
234
235// generationContextReader stops a long read, an image hash, once its
236// context ends.
237type generationContextReader struct {
238 ctx context.Context
239 r io.Reader
240}
241
242func (c generationContextReader) Read(p []byte) (int, error) {
243 if err := c.ctx.Err(); err != nil {
244 return 0, err
245 }
246 return c.r.Read(p)
247}
248
249func rootFloorDowngradeIdle(ctx context.Context) error {
250 l, held, err := rootparticipation.TransactionLedger(ctx)
251 if err != nil {
252 return err
253 }
254 if !held {
255 l, err = rootparticipation.Open()
256 if err != nil {
257 return err
258 }
259 defer l.State.Close()
260 }
261 l.VerifyLaunches = func(ctx context.Context, revision uint64) error {
262 read := func(ctx context.Context) (generationInventory, error) {
263 snapshot, err := productionLocalGenerationInventory(ctx)
264 if err == nil && snapshot.Census.LaunchRevision != revision {
265 return snapshot, errRootFloorHistory
266 }
267 return snapshot, err
268 }
269 state, err := rootstate.Open(release.RootGenerationsDir, false, false)
270 if err != nil {
271 return err
272 }
273 defer state.Close()
274 source := release.RootGenerationPins{Ledger: &release.RootGenerations{Store: state}, Reconcile: (generationReconciler{read: read, kernel: productionGenerationKernelCensus}).reconcile}
275 pins, err := source.StagedPins(ctx, release.OptDir())
276 if err != nil {
277 return err
278 }
279 if len(pins) > 0 {
280 return errRootFloorHistory
281 }
282 return nil
283 }
284 return l.Idle(ctx)
285}
286
287func rootRefreshAndJudgeMovesPrev(id rootFloorIdentity) error {
288 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
289 defer cancel()
290 opt, dd := release.OptDir(), dataDir()
291 // The adopt pass already owns release exclusion. Acquire no release lock here.
292 return withPrevLock(opt, func() error {
293 f, err := openRootMovesFloors(opt, id, true)
294 if err != nil {
295 return err
296 }
297 defer f.close()
298 j := rootFloorJudge{floors: f, current: systemBinaryPath(opt), prev: systemPrevPath(opt), idle: rootFloorDowngradeIdle,
299 binary: func(path string) (rootFloorBinary, error) { return readRootFloorBinary(opt, path) },
300 legacy: func() ([]*moves.Floor, error) { return rootFloorLegacy(dd) },
301 remote: func(ctx context.Context) (*moves.Floor, error) {
302 s, err := openDeployStore(ctx)
303 if err != nil {
304 return nil, err
305 }
306 return moves.ReadFloor(ctx, s)
307 }}
308 return j.refresh(ctx)
309 })
310}
311
312func withRootMovesFields(f edgesFile, opt string) edgesFile {
313 f.MovesProtocol = moves.ProtocolVersion
314 f.MovesFloorState = "quarantined"
315 f.MovesFloorMinRelease, f.PrevRelease, f.PrevAllowedFor = "", "", ""
316 p, err := readRootFloorProjection(opt)
317 if err != nil {
318 return f
319 }
320 if p.Identity.Deployment != os.Getenv("CARLOS_DEPLOYMENT_BUCKET") || p.Identity.Box != f.Box {
321 return f
322 }
323 public, err := rootstate.Open(rootFloorDir(opt), false, true)
324 if err != nil {
325 return f
326 }
327 defer public.Close()
328 ad, err := rootFloorAdvertisementFor(public, p)
329 if err != nil {
330 return f
331 }
332 if rootstate.Sync(public) != nil {
333 return f
334 }
335 f.MovesFloorState, f.MovesFloorGeneration = ad.State, ad.Generation
336 f.MovesFloorMinRelease, f.PrevRelease, f.PrevAllowedFor = ad.MinRelease, ad.PrevRelease, ad.PrevAllowedFor
337 return f
338}
339