package activator import ( "context" "errors" "os" "path/filepath" "strings" "testing" "time" "github.com/carlosframework/platform/internal/instancekey" "github.com/carlosframework/platform/internal/registry" ) // Ruling 29 (Astra round 6 finding 1), deterministic: the launch reads a // configured key A, and then a config sync removes the key from this box's // .config.env (the bucket no longer holds it) before the replica // gate runs. The gate decides on the layer the launch's own read found, so // it still consults the bucket, which now renders no key, and refuses. A // second read of the files would have found no key, skipped the bucket // and admitted a child that is handed A. func TestReplicaGateDecidesOnTheLaunchesOwnRead(t *testing.T) { tok := "CARLOS_ADMIN_TOKEN=" + strings.Repeat("c", 64) + "\n" keyA := strings.Repeat("A", 43) + "=" b := testExecBackend(t) rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true, Socket: noControlSocket(t)} cfgPath := filepath.Join(b.DataDir, rt.Host+".config.env") if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".env"), []byte(tok), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(cfgPath, []byte(InstanceKeyVar+"="+keyA+"\n"), 0o600); err != nil { t.Fatal(err) } b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(keyA), true } b.ConfiguredKey = func(context.Context, registry.Route) (string, error) { return "", nil } // the bucket: key removed syncs := 0 instanceKeyHooks.replicaGate = func() { syncs++ if err := os.WriteFile(cfgPath, []byte("OTHER=1\n"), 0o600); err != nil { t.Error(err) } } t.Cleanup(func() { instanceKeyHooks.replicaGate = nil }) 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 syncs != 1 { t.Fatalf("the sync ran %d times between the read and the gate, want once", syncs) } if got := outcomeOf(out); !errors.Is(err, ErrReplicaKey) || got != "" { t.Fatalf("Start = %v, app outcome %q; want ErrReplicaKey and no app", err, got) } } // The gate reads no env file: given the env and layer the launch's read // captured, it decides the same with the files gone. func TestReplicaGateReadsNoEnvFile(t *testing.T) { keyA := strings.Repeat("A", 43) + "=" b := &ExecBackend{DataDir: t.TempDir()} rt := registry.Route{Host: "shop-au.example.com", Kind: "instance", Replica: true} if err := os.WriteFile(filepath.Join(b.DataDir, rt.Host+".config.env"), []byte(InstanceKeyVar+"="+keyA+"\n"), 0o600); err != nil { t.Fatal(err) } env, layer, err := b.instanceEnvLayer(rt) if err != nil || layer != instancekey.LayerConfig || env[InstanceKeyVar] != keyA { t.Fatalf("instanceEnvLayer = %v, %v", layer, err) } if err := os.RemoveAll(b.DataDir); err != nil { t.Fatal(err) } b.ReplicaHomeKeys = func(string) (string, bool) { return instancekey.Digest(keyA), true } for _, tc := range []struct { bucket string ok bool }{{keyA, true}, {"", false}} { b.ConfiguredKey = func(context.Context, registry.Route) (string, error) { if tc.bucket == "" { return "", nil } return instancekey.Digest(tc.bucket), nil } if err := b.replicaKeyGate(rt, env, layer, false); (err == nil) != tc.ok { t.Fatalf("bucket %q: gate = %v, want ok %v", tc.bucket, err, tc.ok) } } if err := b.replicaKeyGate(rt, env, instancekey.LayerNone, false); !errors.Is(err, ErrReplicaKey) { t.Fatalf("a key with no known layer = %v, want a refusal", err) } }