carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux && rootrunner
2
3package host
4
5import (
6 "crypto/ed25519"
7 "encoding/json"
8 "os"
9 "os/exec"
10 "strings"
11 "testing"
12
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostfloor"
15 "github.com/carlosframework/platform/internal/release"
16)
17
18func lifecycleArmGuard(t *testing.T, store release.Store, key ed25519.PrivateKey, actor hostbox.Actor, pointer release.SystemPointer, resume bool) {
19 t.Helper()
20 endpoint := activationS3Fixture(t, store)
21 env := []string{"PLAN14_LIFECYCLE_GUARD=1", "CARLOS_DEPLOYMENT_BUCKET=" + actor.ControlBucket, "CARLOS_S3_ENDPOINT=" + endpoint, "AWS_REGION=eu-west-1", "AWS_DEFAULT_REGION=eu-west-1"}
22 for _, name := range []string{"CARLOS_RELEASE_PUBKEY", "CARLOS_DATA_DIR", "CARLOS_OPT_DIR", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_EC2_METADATA_DISABLED", "AWS_CONFIG_FILE", "AWS_SHARED_CREDENTIALS_FILE"} {
23 env = append(env, name+"="+os.Getenv(name))
24 }
25 lifecycleCheck(t, "guard environment", os.WriteFile("/etc/carlos/host.env", []byte(strings.Join(env, "\n")+"\n"), 0600))
26 for _, unit := range []string{"carlos-edge.service", "carlos-system-update.service", "carlos-system-recover.service"} {
27 path := "/etc/systemd/system/" + unit + ".d"
28 if !resume {
29 if _, err := os.Lstat(path); !os.IsNotExist(err) {
30 t.Fatal("pre-existing guard drop-in", path, err)
31 }
32 }
33 t.Cleanup(func() {
34 if os.Getenv("PLAN14_LIFECYCLE_PHASE") != "1" || t.Failed() {
35 os.RemoveAll(path)
36 }
37 exec.Command("systemctl", "daemon-reload").Run()
38 })
39 }
40 if !resume {
41 floor, err := json.Marshal(hostfloor.Floor{Min: 1, MinRelease: "v2.0.0"})
42 lifecycleCheck(t, "guard floor", err)
43 auth, err := release.SignRootEnvelope(release.RootSigner(key), hostfloor.Authorization{Schema: 1, Purpose: "moves-protocol", DeploymentBucket: actor.ControlBucket, Key: hostfloor.Key, SHA256: hostfloor.Digest(floor)})
44 lifecycleCheck(t, "guard authorization", err)
45 policy, err := json.Marshal(hostfloor.Policy{Schema: 1, DeploymentBucket: actor.ControlBucket, Mode: "armed", Floor: floor, Authorization: auth})
46 lifecycleCheck(t, "guard policy", err)
47 identity, err := release.SignRootEnvelope(release.RootSigner(key), pointer)
48 lifecycleCheck(t, "guard signed identity", err)
49 enrollment, err := json.Marshal(hostfloor.Enrollment{Box: actor.Box, Enrollment: actor.Enrollment, InstanceID: actor.Cloud.InstanceID})
50 lifecycleCheck(t, "guard enrollment", err)
51 for name, raw := range map[string][]byte{"policy": policy, "identity": identity, "enrollment": enrollment} {
52 lifecycleCheck(t, "guard input", os.WriteFile("/etc/carlos/"+name+".json", raw, 0600))
53 }
54 cmd := exec.Command("/opt/carlos/carlos", "system", "guard-arm", "--policy", "/etc/carlos/policy.json", "--identity", "/etc/carlos/identity.json", "--enrollment", "/etc/carlos/enrollment.json")
55 cmd.Env = append(env, "PATH=/usr/sbin:/usr/bin:/sbin:/bin")
56 out, err := cmd.CombinedOutput()
57 lifecycleCheck(t, "real guard-arm: "+string(out), err)
58 }
59 raw, err := os.ReadFile("/etc/systemd/system/carlos-edge.service.d/90-host-floor.conf")
60 lifecycleCheck(t, "guard drop-in", err)
61 if string(raw) != "[Unit]\nRequires=carlos-system-recover.service\nAfter=carlos-system-recover.service\n[Service]\nExecStart=\nExecStart=/opt/carlos/system/guard/carlos system guard-exec edge\n" {
62 t.Fatal("edge does not retain guard-arm drop-in", string(raw))
63 }
64 guard, err := os.Stat("/opt/carlos/system/guard/carlos")
65 lifecycleCheck(t, "guard inode", err)
66 current, err := os.Stat("/opt/carlos/carlos")
67 lifecycleCheck(t, "current inode", err)
68 if os.SameFile(guard, current) {
69 t.Fatal("guard must be an independent copy")
70 }
71}
72