carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package objstore
2
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "crypto/rand"
8 "encoding/json"
9 "errors"
10 "os"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/release"
15)
16
17// The scanning block round-trips, and its three sub-blocks are null-or-present
18// so a client can tell "requested, waiting for an operator" from "enabled"
19// without inspecting fields — the same tri-state the store's own blocks give.
20func TestScanBlockRoundTrip(t *testing.T) {
21 now := time.Now().UTC().Truncate(time.Second)
22 rec := &Record{
23 App: "titogo",
24 Scanning: &ScanBlock{
25 Requested: &ScanRequested{At: now, By: "m@example.com"},
26 Enabled: &ScanEnabled{Engine: "clamav", At: now, By: "op@example.com"},
27 Enforce: &DenyTag{Key: TagVerdict, Value: string(VerdictInfected)},
28 },
29 }
30 body, err := json.Marshal(rec)
31 if err != nil {
32 t.Fatal(err)
33 }
34 var back Record
35 if err := json.Unmarshal(body, &back); err != nil {
36 t.Fatal(err)
37 }
38 if back.Scanning == nil || back.Scanning.Enabled == nil {
39 t.Fatalf("scanning.enabled did not round-trip: %s", body)
40 }
41 if back.Scanning.Enabled.Engine != "clamav" {
42 t.Fatalf("engine = %q, want clamav", back.Scanning.Enabled.Engine)
43 }
44 if back.Scanning.Enforce == nil || back.Scanning.Enforce.Value != string(VerdictInfected) {
45 t.Fatalf("enforce did not round-trip: %s", body)
46 }
47}
48
49// An absent scanning block stays absent. Every deployment that never touches
50// scanning must not grow a null key in every store record it owns.
51func TestScanBlockOmittedWhenAbsent(t *testing.T) {
52 body, err := json.Marshal(&Record{App: "titogo"})
53 if err != nil {
54 t.Fatal(err)
55 }
56 if bytes.Contains(body, []byte("scanning")) {
57 t.Fatalf("an absent scanning block leaked into the JSON: %s", body)
58 }
59}
60
61// The attestation is a real field, not an inference: a store whose objects
62// arrive already encrypted cannot be scanned, and the record has to say so
63// because nothing can tell by looking (spec §2.2).
64func TestScanRequestedCarriesEncryptedAttestation(t *testing.T) {
65 body, err := json.Marshal(&ScanRequested{
66 At: time.Now().UTC(), By: "m@example.com", Encrypted: true,
67 })
68 if err != nil {
69 t.Fatal(err)
70 }
71 var back ScanRequested
72 if err := json.Unmarshal(body, &back); err != nil {
73 t.Fatal(err)
74 }
75 if !back.Encrypted {
76 t.Fatalf("the encrypted attestation did not round-trip: %s", body)
77 }
78}
79
80// The scanning block is ADDITIVE. release.PurgeApp parses this shape with its
81// own local struct — it cannot import objstore, because objstore imports
82// release — and must keep refusing on grant/provisioned while ignoring keys it
83// has never heard of. objstore.go's PARSER LIST comment claims exactly this,
84// and TestPurgeGuardSeesOurRecord pins the two names without ever writing a
85// scanning key, so without this test the additive half of the claim is
86// unasserted.
87//
88// Driven through release.PurgeApp itself rather than an extracted predicate:
89// the guard is inline in PurgeApp, and pulling it out to make it callable
90// would refactor the fleet-wide purge path for the sake of a test.
91func TestPurgeGuardIgnoresScanningBlock(t *testing.T) {
92 ctx := context.Background()
93 _, pub := testSealKeypair(t)
94 relPub, _, err := ed25519.GenerateKey(rand.Reader)
95 if err != nil {
96 t.Fatal(err)
97 }
98 backdated := time.Now().Add(-2 * release.TrashCooloff)
99
100 // A record naming backend estate still blocks the purge with a scanning
101 // block sitting beside it — the new key must not shadow the guard.
102 t.Run("provisioned-plus-scanning-still-blocks", func(t *testing.T) {
103 s := release.NewDirStore(t.TempDir())
104 p := NewMemProvisioner("eu-west-1")
105 rotateFixture(t, s, pub, p)
106
107 stored, etag, err := Get(ctx, s, "bab", "titogo")
108 if err != nil || stored == nil {
109 t.Fatalf("re-read: %+v, %v", stored, err)
110 }
111 stored.Scanning = &ScanBlock{
112 Enabled: &ScanEnabled{Engine: "clamav", At: time.Now().UTC(), By: "op@example.com"},
113 }
114 if _, err := Put(ctx, s, "bab", "titogo", stored, etag); err != nil {
115 t.Fatal(err)
116 }
117 if err := release.TrashApp(ctx, s, "bab", "titogo", "paul@keymail.dev", backdated); err != nil {
118 t.Fatal(err)
119 }
120 err = release.PurgeApp(ctx, s, relPub, "bab", "titogo", time.Now())
121 if !errors.Is(err, release.ErrStoreProvisioned) {
122 t.Fatalf("PurgeApp over provisioned+scanning = %v, want release.ErrStoreProvisioned", err)
123 }
124 })
125
126 // The other direction, and the one that proves the key is genuinely
127 // ignored rather than accidentally load-bearing: scanning alone names no
128 // backend estate, so it must not block a purge.
129 t.Run("scanning-alone-names-no-estate", func(t *testing.T) {
130 s := release.NewDirStore(t.TempDir())
131 declare(t, s, "bab", "titogo", "TITO_S3")
132
133 stored, etag, err := Get(ctx, s, "bab", "titogo")
134 if err != nil || stored == nil {
135 t.Fatalf("re-read: %+v, %v", stored, err)
136 }
137 stored.Scanning = &ScanBlock{
138 Requested: &ScanRequested{At: time.Now().UTC(), By: "m@example.com"},
139 }
140 if _, err := Put(ctx, s, "bab", "titogo", stored, etag); err != nil {
141 t.Fatal(err)
142 }
143 if err := release.TrashApp(ctx, s, "bab", "titogo", "paul@keymail.dev", backdated); err != nil {
144 t.Fatal(err)
145 }
146 if err := release.PurgeApp(ctx, s, relPub, "bab", "titogo", time.Now()); err != nil {
147 t.Fatalf("PurgeApp over a declared+scanning record: %v", err)
148 }
149 if _, _, err := s.Get(ctx, Key("bab", "titogo")); !errors.Is(err, os.ErrNotExist) {
150 t.Fatalf("store.json survived the purge: %v", err)
151 }
152 })
153}
154