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 "database/sql"
7 "encoding/json"
8 "errors"
9 "time"
10
11 "github.com/carlosframework/platform/internal/hostbox"
12 "github.com/carlosframework/platform/internal/hostguard"
13)
14
15var ErrClaimStopControllerAlive = errors.New("claim stop controller is still alive")
16
17type stopRecoveryKernel interface {
18 RecoverStopped(context.Context, hostguard.FenceTarget, StopInventory) (StopKernelEvidence, DrainAcknowledgement, error)
19}
20type exactStopInventory interface {
21 ReadExactStopInventory(context.Context, hostguard.FenceTarget) (StopInventory, error)
22}
23
24func (c ClaimStopCoordinator) verifyUnjournaled(ctx context.Context, target hostguard.FenceTarget, in StopInventory, cause error) (StopKernelEvidence, error) {
25 kernel, ok := c.Kernel.(interface {
26 VerifyUnjournaled(context.Context, hostguard.FenceTarget, StopInventory) (StopKernelEvidence, error)
27 })
28 if !ok || c.Guard.Root == nil || !errors.Is(cause, sql.ErrNoRows) {
29 return StopKernelEvidence{}, cause
30 }
31 if err := c.Guard.ResumeFence(ctx, target); err != nil {
32 return StopKernelEvidence{}, err
33 }
34 return kernel.VerifyUnjournaled(ctx, target, in)
35}
36
37func (c ClaimStopCoordinator) recoverKernel(ctx context.Context, kernel stopRecoveryKernel, target hostguard.FenceTarget, in StopInventory) (StopKernelEvidence, DrainAcknowledgement, error) {
38 evidence, ack, err := kernel.RecoverStopped(ctx, target, in)
39 if err != nil {
40 evidence, err = c.verifyUnjournaled(ctx, target, in, err)
41 if err == nil {
42 ack = DrainClaimAcknowledgement(target.FenceID, target.ControllerID, "unlaunched", nil, hostbox.ProducerCompletion{})
43 }
44 }
45 return evidence, ack, err
46}
47
48func (c ClaimStopCoordinator) RecoverStopped(ctx context.Context, target hostguard.FenceTarget) (StopResult, error) {
49 kernel, ok := c.Kernel.(stopRecoveryKernel)
50 if !ok || c.Now == nil || c.NewID == nil || len(c.EvidenceSigner) != ed25519.PrivateKeySize || c.RequestLimit != hostbox.RequestLimit || !stopOperationTuple(target.Op, target.Phase, target.ObligationID) {
51 return StopResult{}, ErrClaimStopUnavailable
52 }
53 inventory, ok := c.Inventory.(exactStopInventory)
54 if !ok {
55 return StopResult{}, ErrClaimStopInventory
56 }
57 // A fence persisted without a roster numeral cannot produce a receipt the
58 // console accepts; refuse it before any kill (roster binding N1).
59 numeral, err := stopReceiptNumeral(target)
60 if err != nil {
61 return StopResult{}, err
62 }
63 if err := c.Guard.ResumeFence(ctx, target); err != nil {
64 return StopResult{}, err
65 }
66 in, err := inventory.ReadExactStopInventory(ctx, target)
67 if err != nil {
68 return StopResult{}, err
69 }
70 if in.Host != target.Host || in.Incarnation != target.Incarnation || in.InstallationStamp != target.Stamp || in.Box != target.Box || in.Enrollment != target.Enrollment || in.DirectoryRevision != target.DirectoryRevision {
71 return StopResult{}, ErrClaimStopInventory
72 }
73 evidence, ack, err := c.recoverKernel(ctx, kernel, target, in)
74 if err != nil {
75 return StopResult{}, err
76 }
77 if ack.FenceID != target.FenceID || ack.ControllerID != target.ControllerID || ack.ProcessStart == "" || !stopDigest(ack.Digest) {
78 return StopResult{}, ErrClaimStopIdentity
79 }
80 id := c.NewID()
81 if !stopComponent(id) {
82 return StopResult{}, ErrClaimStopUnavailable
83 }
84 at := c.Now().UTC()
85 if at.IsZero() || !stopDigest(evidence.InventorySHA256) || !stopDigest(evidence.LocalJournalSHA256) || len(evidence.CgroupIdentities) == 0 {
86 return StopResult{}, ErrClaimStopReceipt
87 }
88 receipt := StopReceipt{RequestLimitNS: int64(c.RequestLimit), Region: target.Region, ClaimETag: target.ClaimETag, ObservedBootID: evidence.ObservedBootID, V: 1, ID: id, ClaimGen: target.ClaimGen, Host: target.Host, Incarnation: target.Incarnation, InstallationStamp: target.Stamp, WriterSession: target.Session, OperationID: target.Op, Phase: target.Phase, ObligationID: target.ObligationID, Box: target.Box, Enrollment: target.Enrollment, BootID: target.BootID, ControllerID: target.ControllerID, DirectoryRevision: numeral, Binding: target.DirectoryRevision, Kind: "stopped", EvidenceAt: at, FenceID: target.FenceID, InventorySHA256: evidence.InventorySHA256, LocalJournalSHA256: evidence.LocalJournalSHA256, ProducerDrainDigest: ack.Digest, CgroupIdentities: evidence.CgroupIdentities, UnitInvocationIDs: evidence.UnitInvocationIDs}
89 body, err := json.Marshal(receipt)
90 if err != nil {
91 return StopResult{}, err
92 }
93 if err := c.Guard.CompleteFence(ctx, target, body, at, func(context.Context, hostguard.FenceTarget, []byte, time.Time) error { return nil }); err != nil {
94 return StopResult{}, err
95 }
96 return StopResult{Target: target, Key: stopEvidenceKey(target.Host, target.Op, target.Box, id), Digest: digestRawStop(body), Bytes: body, EvidenceAt: at}, nil
97}
98func (h ClaimStopHTTP) RecoverPending(ctx context.Context) error {
99 targets, err := h.Coordinator.Guard.PendingStopFences(ctx)
100 if err != nil {
101 return err
102 }
103 for _, target := range targets {
104 if _, err := h.Coordinator.RecoverStopped(ctx, target); err != nil && !errors.Is(err, ErrClaimStopControllerAlive) {
105 return err
106 }
107 }
108 return nil
109}
110