carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1// What a named unit is running, established from the bytes — D3 of the
2// console release defences (spec 2026-10-06-console-release-defences-design.md
3// §4.2, §4.3, §4.5).
4//
5// observe checks the first three of spec §4.3's mint conditions: the binding
6// is verified, the unit is active in one invocation for at least `after`, the
7// sha256 of /proc/<MainPID>/exe equals the live link's bytes, and the route is
8// ready now. The root pass mints on it (proofroot.go) and records the build
9// in the healthy history on it. recordSettled is the other way into that
10// history: the hook D1's checkedUnitRestart runs when the settle check passes.
11//
12// Both paths hash the running executable's bytes. A sha256 is never taken
13// from a manifest or a version directory's name: the only build a box may
14// vouch for is the one it is running.
15package host
16
17import (
18 "context"
19 "fmt"
20 "os"
21 "time"
22
23 "github.com/carlosframework/platform/internal/activator"
24 "github.com/carlosframework/platform/internal/registry"
25 "github.com/carlosframework/platform/internal/release"
26)
27
28type liveBuild struct {
29 binding proofBinding
30 SHA256 string
31 Version string
32 ActiveSince time.Time
33 // The process observed, so a later look can tell it is the same one.
34 invocation string
35 pid int
36 start string
37}
38
39type proofObserver struct {
40 resolve proofBindingResolver
41 sys unitSystem
42 procExe func(pid int) string // "/proc/<pid>/exe" in production
43 optDir string
44 now func() time.Time
45 ready func(rt registry.Route) func() error // routeReady in production; nil func = no upstream
46}
47
48func productionProofObserver(optDir string) proofObserver {
49 return proofObserver{sys: systemdUnits{}, procExe: procExePath, optDir: optDir, now: time.Now,
50 resolve: productionRootRestartChecks(systemdUnits{}, optDir).resolve,
51 ready: func(rt registry.Route) func() error { return proofRouteReady(rt, 2*time.Second) }}
52}
53
54func procExePath(pid int) string { return fmt.Sprintf("/proc/%d/exe", pid) }
55
56func (o proofObserver) observe(rt registry.Route, after time.Duration) (liveBuild, error) {
57 if o.resolve == nil {
58 return liveBuild{}, os.ErrPermission
59 }
60 binding, err := o.resolve(context.Background(), rt)
61 if err != nil {
62 return liveBuild{}, err
63 }
64 if binding.Source == "" {
65 return liveBuild{}, os.ErrPermission
66 }
67 rt = binding.Route
68 link := release.LiveLink(o.optDir, rt.Host)
69 before, err := o.sys.Show(rt.Unit)
70 if err != nil {
71 return liveBuild{}, fmt.Errorf("cannot read %s: %v", rt.Unit, err)
72 }
73 if before.ExecPath != proofExecPath(binding, o.optDir) {
74 return liveBuild{}, fmt.Errorf("binding: %s runs %s, not %s", rt.Unit, before.ExecPath, link)
75 }
76 if before.ActiveState != "active" || before.MainPID <= 0 {
77 return liveBuild{}, fmt.Errorf("%s is %s", rt.Unit, before.ActiveState)
78 }
79 if before.ActiveEnterTimestamp.IsZero() {
80 return liveBuild{}, fmt.Errorf("%s reports no ActiveEnterTimestamp", rt.Unit)
81 }
82 start, err := o.sys.StartTime(before.MainPID)
83 if err != nil || start == "" {
84 return liveBuild{}, os.ErrPermission
85 }
86 if up := o.now().Sub(before.ActiveEnterTimestamp); up < after {
87 return liveBuild{}, fmt.Errorf("%s has been active for %s, needs %s", rt.Unit, up.Round(time.Second), after)
88 }
89 exe, err := release.FileSHA256(o.procExe(before.MainPID))
90 if err != nil {
91 return liveBuild{}, fmt.Errorf("hashing the running executable: %v", err)
92 }
93 live, err := release.FileSHA256(link)
94 if err != nil {
95 return liveBuild{}, fmt.Errorf("hashing the live link's build: %v", err)
96 }
97 if exe != live {
98 return liveBuild{}, fmt.Errorf("the running executable (sha256 %s) is not the live link's build (sha256 %s)", exe, live)
99 }
100 if binding.Network {
101 if o.ready == nil {
102 return liveBuild{}, os.ErrPermission
103 }
104 if ready := o.ready(rt); ready != nil {
105 if err := ready(); err != nil {
106 return liveBuild{}, fmt.Errorf("not ready: %v", err)
107 }
108 } else {
109 return liveBuild{}, os.ErrPermission
110 }
111 }
112 again, err := o.sys.Show(rt.Unit)
113 if err != nil || again.ActiveState != "active" || again.InvocationID != before.InvocationID || again.MainPID != before.MainPID {
114 return liveBuild{}, fmt.Errorf("%s restarted during the check", rt.Unit)
115 }
116 current, err := o.resolve(context.Background(), rt)
117 if err != nil || current.Source != binding.Source {
118 return liveBuild{}, os.ErrPermission
119 }
120 againExe, err := release.FileSHA256(o.procExe(again.MainPID))
121 if err != nil || againExe != exe || again.ExecPath != before.ExecPath {
122 return liveBuild{}, os.ErrPermission
123 }
124 againStart, err := o.sys.StartTime(again.MainPID)
125 if err != nil || againStart != start {
126 return liveBuild{}, os.ErrPermission
127 }
128 ver, _ := release.LiveVersion(o.optDir, rt.Host)
129 return liveBuild{SHA256: exe, Version: ver, ActiveSince: before.ActiveEnterTimestamp,
130 invocation: before.InvocationID, pid: before.MainPID, start: start, binding: binding}, nil
131}
132
133func (o proofObserver) recordSettled(history string) func(registry.Route) error {
134 return func(rt registry.Route) error {
135 if rt.Unit == "" || rt.Unit == activator.UnitName(rt.Host) {
136 return nil
137 }
138 b, err := o.observe(rt, 0)
139 if err != nil {
140 return err
141 }
142 return release.RecordHealthy(history, b.binding.Route.Account, b.binding.Route.App,
143 release.HealthyBuild{SHA256: b.SHA256, Version: b.Version, How: "settle", At: o.now().UTC()})
144 }
145}
146