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 "io"
9 "os"
10 "path/filepath"
11 "strings"
12
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/hostguard"
16 "github.com/carlosframework/platform/internal/instancectl"
17 "github.com/carlosframework/platform/internal/provision"
18 "github.com/carlosframework/platform/internal/release"
19 "github.com/carlosframework/platform/internal/rootstate"
20)
21
22type rootGenerationSource struct {
23 beginUpdate func(context.Context, *rootGenerationSource, release.GenerationBinding) (func(string, error, func() error) error, func() error, error)
24 store release.Store
25 optDir string
26 pub func() (ed25519.PublicKey, error)
27 open func() (rootstate.Store, error)
28 openFile func(string) (*os.File, error)
29 persistent func(*os.File) (rootstate.FileIdentity, error)
30}
31
32func productionRootGenerations(store release.Store, optDir string) *rootGenerationSource {
33 return &rootGenerationSource{beginUpdate: beginRootGenerationUpdate, store: store, optDir: optDir,
34 pub: func() (ed25519.PublicKey, error) {
35 return release.ParsePublicKey(os.Getenv("CARLOS_RELEASE_PUBKEY"), "CARLOS_RELEASE_PUBKEY")
36 },
37 open: func() (rootstate.Store, error) { return rootstate.Open(release.RootGenerationsDir, true, false) },
38 openFile: rootstate.OpenTrustedFile, persistent: rootstate.PersistentIdentity}
39}
40
41// resolve runs under release and host exclusion. The request nominates a build;
42// root binds it to its installation, the deployment record and signed manifest.
43func (s *rootGenerationSource) resolve(ctx context.Context, h *ClaimLaunchRoot, peer StopPeer, q hostbox.RoleLaunchRequest) (release.GenerationBinding, error) {
44 var b release.GenerationBinding
45 if s == nil || s.store == nil || s.pub == nil || s.openFile == nil || h.Guard.Root == nil || q.Reservation == nil || q.Spec == nil {
46 return b, os.ErrPermission
47 }
48 t := q.Reservation.Target
49 in, err := h.Guard.Root.Installation(ctx, t.Host, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp})
50 if err != nil {
51 return b, err
52 }
53 if in.Host != t.Host || in.Incarnation != t.Incarnation || in.Stamp != t.Stamp || in.Box != t.Box || in.Enrollment != t.Enrollment || in.InventoryRevision == 0 || in.Record != provision.RecordKey(in.Account, in.App, in.Host) {
54 return b, os.ErrPermission
55 }
56 rec, etag, err := provision.GetRecordETag(ctx, s.store, in.Account, in.App, in.Host)
57 if err != nil {
58 return b, err
59 }
60 inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt)
61 if err != nil || inc != in.Incarnation || rec.Account != in.Account || rec.App != in.App || rec.Host != in.Host || etag == "" {
62 return b, os.ErrPermission
63 }
64 rel, err := filepath.Rel(filepath.Join(s.optDir, "versions"), q.Executable)
65 if err != nil || !filepath.IsLocal(rel) {
66 return b, os.ErrPermission
67 }
68 version, _, ok := strings.Cut(rel, string(filepath.Separator))
69 if !ok || !release.ValidVersion(version) || q.Executable != release.VersionPath(s.optDir, in.Account, version, in.App) {
70 return b, os.ErrPermission
71 }
72 pub, err := s.pub()
73 if err != nil {
74 return b, err
75 }
76 m, err := release.AuthorizedManifest(ctx, s.store, pub, in.Account, in.App, version)
77 if err != nil {
78 return b, err
79 }
80 if m.Kind != "binary" || m.Artifacts[release.AdoptPlatform()].SHA256 != q.Spec.Digest {
81 return b, os.ErrPermission
82 }
83 f, err := s.openFile(q.Executable)
84 if err != nil {
85 return b, err
86 }
87 defer f.Close()
88 id, err := rootstate.Identity(f)
89 if err != nil {
90 return b, err
91 }
92 if id.Device != q.Reservation.Executable.Device || id.Inode != q.Reservation.Executable.Inode {
93 return b, os.ErrPermission
94 }
95 hash := sha256.New()
96 if _, err := io.Copy(hash, f); err != nil {
97 return b, err
98 }
99 if hex.EncodeToString(hash.Sum(nil)) != q.Spec.Digest {
100 return b, os.ErrPermission
101 }
102 // OpenTrustedFile already checked this ancestry; release exclusion prevents
103 // another root release writer replacing it while the identities are captured.
104 tree := filepath.Dir(q.Executable)
105 d, err := os.Open(tree)
106 if err != nil {
107 return b, err
108 }
109 defer d.Close()
110 treeID, err := rootstate.Identity(d)
111 if err != nil {
112 return b, err
113 }
114 b = release.GenerationBinding{Binding: release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App},
115 Target: release.GenerationOwner{Host: t.Host, Incarnation: t.Incarnation, Stamp: t.Stamp, Session: t.Session, Box: t.Box, Enrollment: t.Enrollment, BootID: peer.BootID, ControllerID: peer.ControllerID},
116 Installation: rootHistoryInstallation(in), Record: in.Record, RecordETag: etag,
117 Attempt: q.Reservation.ID, Inputs: q.Reservation.Inputs, Invocation: q.Reservation.InvocationID, ControllerPID: peer.PID, ControllerStart: peer.ProcessStart,
118 Version: version, Digest: q.Spec.Digest, Path: q.Executable, Tree: tree, Executable: id, TreeIdentity: treeID}
119 if s.persistent != nil {
120 b.ExecutableProof, err = s.persistent(f)
121 if err != nil {
122 return release.GenerationBinding{}, err
123 }
124 b.TreeProof, err = s.persistent(d)
125 if err != nil {
126 return release.GenerationBinding{}, err
127 }
128 if b.ExecutableProof.Boot != peer.BootID || b.TreeProof.Boot != peer.BootID {
129 return release.GenerationBinding{}, os.ErrPermission
130 }
131 }
132 unit, group, err := claimLaunchPlacement(h.Units, t.Host)
133 if err != nil || group != q.Reservation.Group.RelativePath {
134 return release.GenerationBinding{}, os.ErrPermission
135 }
136 if unit != instancectl.UnitName(instancectl.RoleRun, t.Host) {
137 b.Unit = unit
138 }
139 return b, nil
140}
141
142type generationLaunchPermit struct {
143 finishUpdate func(string, error, func() error) error
144 closeUpdate func() error
145 ledger *release.RootGenerations
146 reservation release.GenerationReservation
147 checkBinding func() (release.GenerationBinding, error)
148}
149
150func (s *rootGenerationSource) reserve(ctx context.Context, h *ClaimLaunchRoot, peer StopPeer, q hostbox.RoleLaunchRequest) (*generationLaunchPermit, error) {
151 if s == nil || s.open == nil {
152 return nil, os.ErrPermission
153 }
154 b, err := s.resolve(ctx, h, peer, q)
155 if err != nil {
156 return nil, err
157 }
158 store, err := s.open()
159 if err != nil {
160 return nil, err
161 }
162 g := &release.RootGenerations{Store: store}
163 r, err := g.Reserve(b)
164 if err != nil {
165 store.Close()
166 return nil, err
167 }
168 p := &generationLaunchPermit{ledger: g, reservation: r, checkBinding: func() (release.GenerationBinding, error) { return s.resolve(ctx, h, peer, q) }}
169 if s.beginUpdate != nil {
170 p.finishUpdate, p.closeUpdate, err = s.beginUpdate(ctx, s, b)
171 if err != nil {
172 store.Close()
173 return nil, err
174 }
175 }
176 return p, nil
177}
178
179func (p *generationLaunchPermit) check() error {
180 if p == nil {
181 return nil
182 }
183 b, err := p.checkBinding()
184 if err != nil {
185 return err
186 }
187 if b != p.reservation.Binding {
188 return os.ErrPermission
189 }
190 return p.ledger.Check(p.reservation)
191}
192
193func (p *generationLaunchPermit) started(grant hostbox.RoleLaunchGrant, observed ClaimLaunchObserved) error {
194 if p == nil {
195 return nil
196 }
197 containment := uint8(0)
198 if observed.Launcher.Device != 0 && observed.Launcher.Inode != 0 {
199 containment = 1
200 }
201 return p.ledger.Started(p.reservation, release.GenerationProcess{Containment: containment, LauncherIdentity: rootstate.FileIdentity{Device: observed.Launcher.Device, Inode: observed.Launcher.Inode}, PID: grant.PID, Start: grant.ProcessStart, TenantPID: observed.TenantPID, TenantStart: observed.TenantStart,
202 Group: grant.Group.RelativePath, Invocation: grant.UnitInvocationID, GroupIdentity: rootstate.FileIdentity{Device: observed.Group.Device, Inode: observed.Group.Inode}})
203}
204