package activator import ( "context" "errors" "net" "os" "path/filepath" "strings" "testing" "time" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/instancekey" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) // socketsAllowed reports whether this process may bind a unix socket (the // sandbox refuses it). func socketsAllowed(t *testing.T) bool { dir, err := os.MkdirTemp("", "ik") // short: a test's own temp dir can exceed a socket path's limit if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) l, err := net.Listen("unix", filepath.Join(dir, "p.sock")) if err != nil { return false } l.Close() return true } // Ruling 26 with a control socket: a keyless replica runs, and the app's // helper refuses at step 3 (ErrNoInstanceKey) instead of minting. func TestKeylessReplicaWithAControlSocketLeavesTheHelperToRefuse(t *testing.T) { if !socketsAllowed(t) { t.Skip("needs unix sockets (run with the sandbox off)") } b := testExecBackend(t) rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true, Socket: tmpSock(t)} if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".env"), []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("c", 64)+"\n"), 0o600); err != nil { t.Fatal(err) } out := stageHelperApp(t, b, rt.Host, "v1") ctx, cancel := recordedCtx(2*time.Second, out) defer cancel() proc, _, _ := b.Start(ctx, rt) if proc != nil { defer proc.Stop(context.Background()) } if got := outcomeOf(out); got != "refused" { t.Fatalf("app outcome %q, want the helper's refusal", got) } } // Ruling 26 on the nested layout: the seed role has no control socket, so // a keyless replica's seed is refused at prepare; a run role handed a key // that is not the home's is refused too; one handed the home's key is // prepared with it. The bucket's fresh read renders the case's own // configured key, so the "another key" refusal is the home-key digest // check's alone, not the fresh read's. func TestNestedReplicaPrepareIsGated(t *testing.T) { homeKey := strings.Repeat("H", 43) + "=" for _, tc := range []struct { name, config string bucket string // the key the bucket's configuration renders now role instancectl.Role refused bool }{ {"a keyless seed", "", "", instancectl.RoleSeed, true}, {"a run role with another key", InstanceKeyVar + "=configured-B\n", "configured-B", instancectl.RoleRun, true}, {"a run role with the home's key", InstanceKeyVar + "=" + homeKey + "\n", homeKey, instancectl.RoleRun, false}, } { t.Run(tc.name, func(t *testing.T) { b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) rt.Replica = true b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(homeKey), true } b.ConfiguredKey = func(context.Context, registry.Route) (string, error) { if tc.bucket == "" { return "", nil } return instancekey.Digest(tc.bucket), nil } dir := provision.InstanceDir(b.DataDir, rt.Host) if err := os.MkdirAll(dir, 0o700); err != nil { t.Fatal(err) } if tc.config != "" { if err := os.WriteFile(filepath.Join(dir, rt.Host+".config.env"), []byte(tc.config), 0o600); err != nil { t.Fatal(err) } } _, ctl, logs, env, err := b.prepare(rt, tc.role) if tc.refused { if !errors.Is(err, ErrReplicaKey) { t.Fatalf("prepare = %v, want ErrReplicaKey", err) } return } if err != nil { t.Fatalf("prepare: %v", err) } logs.Close() if ctl != nil { ctl.Close() } if env[InstanceKeyVar] != homeKey { t.Fatal("the prepared env does not carry the home's key") } }) } } // The nested legacy sidecar role is gated as the flat one is: a key in its // own file must be the home's, and a keyless one is refused (it has no // control socket). func TestNestedReplicaSidecarPrepareIsGated(t *testing.T) { homeKey := strings.Repeat("H", 43) + "=" for _, tc := range []struct { name, sidecar string refused bool }{ {"a keyless sidecar", "OTHER=1\n", true}, {"a sidecar with another key", InstanceKeyVar + "=configured-B\n", true}, {"a sidecar with the home's key", InstanceKeyVar + "=" + homeKey + "\n", false}, } { t.Run(tc.name, func(t *testing.T) { b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) rt.Replica = true b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(homeKey), true } b.ConfiguredKey = func(context.Context, registry.Route) (string, error) { return instancekey.Digest("configured-B"), nil } dir := provision.InstanceDir(b.DataDir, rt.Host) if err := os.MkdirAll(dir, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, rt.Host+"-sidecar.env"), []byte(tc.sidecar), 0o600); err != nil { t.Fatal(err) } _, ctl, logs, env, err := b.prepare(rt, instancectl.RoleSidecar) if tc.refused { if !errors.Is(err, ErrReplicaKey) { t.Fatalf("prepare = %v, want ErrReplicaKey", err) } return } if err != nil { t.Fatalf("prepare: %v", err) } logs.Close() if ctl != nil { ctl.Close() } if env[InstanceKeyVar] != homeKey { t.Fatal("the prepared sidecar env does not carry the home's key") } }) } } // A refused replica launch closes the control listener it had bound: the // socket must not outlive the launch it was bound for, on the flat spawn or // the nested run role. func TestReplicaRefusalClosesTheControlListener(t *testing.T) { if !socketsAllowed(t) { t.Skip("needs unix sockets (run with the sandbox off)") } homeKey := strings.Repeat("H", 43) + "=" homes := func(string) (string, bool) { return instancekey.Digest(homeKey), true } bucketB := func(context.Context, registry.Route) (string, error) { return instancekey.Digest("configured-B"), nil } closed := func(t *testing.T, control string) { t.Helper() if c, err := net.Dial("unix", control); err == nil { c.Close() t.Fatalf("the control socket %s still accepts after the refusal", control) } } t.Run("flat spawn", func(t *testing.T) { b := testExecBackend(t) rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true, Socket: tmpSock(t)} if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".env"), []byte("CARLOS_ADMIN_TOKEN="+strings.Repeat("c", 64)+"\n"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".config.env"), []byte(InstanceKeyVar+"=configured-B\n"), 0o600); err != nil { t.Fatal(err) } b.ReplicaHomeKeys, b.ConfiguredKey = homes, bucketB out := stageHelperApp(t, b, rt.Host, "v1") ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() proc, _, err := b.Start(ctx, rt) if proc != nil { defer proc.Stop(context.Background()) } if !errors.Is(err, ErrReplicaKey) || outcomeOf(out) != "" { t.Fatalf("Start = %v; want ErrReplicaKey and no app", err) } closed(t, rt.Socket+".control") }) t.Run("nested run role", func(t *testing.T) { b, rt := nestedBackend(t, &fakeControl{inv: "inv1"}, &fakeUnit{}) rt.Replica = true b.ReplicaHomeKeys, b.ConfiguredKey = homes, bucketB dir := provision.InstanceDir(b.DataDir, rt.Host) if err := os.MkdirAll(dir, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, rt.Host+".config.env"), []byte(InstanceKeyVar+"=configured-B\n"), 0o600); err != nil { t.Fatal(err) } if _, _, _, _, err := b.prepare(rt, instancectl.RoleRun); !errors.Is(err, ErrReplicaKey) { t.Fatalf("prepare = %v, want ErrReplicaKey", err) } closed(t, rt.Socket+".control") }) }