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 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13// With no deny, the document has exactly two statements and no Condition key,
14// so an enforcement-off store never carries a Deny.
15//
16// This is NOT "byte for byte what it was before scanning existed": the same
17// change adds s3:GetObjectTagging so an app can read the verdict on its own
18// object, which changes the bytes by definition. Every store's policy is
19// rewritten once on its next grant re-walk, deliberately.
20func TestStorePolicyJSONShapeWithoutDeny(t *testing.T) {
21 doc := storePolicyJSON("arn:aws:s3:::b", nil)
22
23 var parsed policyDocument
24 if err := json.Unmarshal([]byte(doc), &parsed); err != nil {
25 t.Fatalf("not valid JSON: %v\n%s", err, doc)
26 }
27 // Two Allows plus the two anti-forgery Denys, which are NOT conditional on
28 // enforcement: an app must not be able to tag its own object whether or
29 // not this store denies reads of infected ones. The old shape asserted
30 // "no Deny at all without enforcement", which was the belief that let the
31 // upload-tagging route stay open.
32 if len(parsed.Statement) != 4 {
33 t.Fatalf("want two Allows and the two anti-forgery Denys, got %d:\n%s",
34 len(parsed.Statement), doc)
35 }
36 if strings.Contains(doc, "s3:ExistingObjectTag") {
37 t.Fatalf("a nil deny must emit no ENFORCEMENT deny:\n%s", doc)
38 }
39 if !strings.Contains(doc, "s3:GetObjectTagging") {
40 t.Fatalf("an app cannot read its own verdict without GetObjectTagging:\n%s", doc)
41 }
42 assertNoPutObjectTagging(t, doc)
43}
44
45// With a deny, a third statement appears, conditioned on the engine's verdict
46// tag.
47func TestStorePolicyJSONWithDeny(t *testing.T) {
48 doc := storePolicyJSON("arn:aws:s3:::b",
49 &DenyTag{Key: TagVerdict, Value: string(VerdictInfected)})
50
51 var parsed policyDocument
52 if err := json.Unmarshal([]byte(doc), &parsed); err != nil {
53 t.Fatalf("not valid JSON: %v\n%s", err, doc)
54 }
55 if len(parsed.Statement) != 5 {
56 t.Fatalf("want five statements with a deny, got %d:\n%s", len(parsed.Statement), doc)
57 }
58 deny := parsed.Statement[4]
59 if deny.Effect != "Deny" || len(deny.Action) != 1 || deny.Action[0] != "s3:GetObject" {
60 t.Fatalf("deny statement = %+v", deny)
61 }
62 if got, _ := deny.Condition["StringEquals"]["s3:ExistingObjectTag/"+TagVerdict].(string); got != "infected" {
63 t.Fatalf("deny does not condition on the verdict tag: %+v", deny.Condition)
64 }
65 assertNoPutObjectTagging(t, doc)
66}
67
68// The anti-forgery property, asserted in one place — and asserted about BOTH
69// routes, because it turned out to have two.
70//
71// The old version of this helper searched the whole document for the string
72// "s3:PutObjectTagging" and failed if it appeared anywhere. That was the right
73// instinct aimed one level too shallow: it proved the permission was not
74// granted, which is not the same as proving an app cannot tag. The second
75// route — x-amz-tagging on the upload — names no action at all, so no search
76// for an action name could ever have found it. See storePolicyJSON's comment
77// for what a real server did with it.
78func assertNoPutObjectTagging(t *testing.T, doc string) {
79 t.Helper()
80
81 var parsed policyDocument
82 if err := json.Unmarshal([]byte(doc), &parsed); err != nil {
83 t.Fatalf("not valid JSON: %v\n%s", err, doc)
84 }
85
86 var deniesCall, deniesHeader bool
87 for _, st := range parsed.Statement {
88 for _, a := range st.Action {
89 if st.Effect == "Allow" && (a == "s3:PutObjectTagging" || a == "s3:DeleteObjectTagging") {
90 t.Fatalf("app must never be able to tag its own objects:\n%s", doc)
91 }
92 if st.Effect == "Deny" && a == "s3:PutObjectTagging" {
93 deniesCall = true
94 }
95 }
96 if st.Effect != "Deny" {
97 continue
98 }
99 keys, _ := st.Condition["ForAnyValue:StringEquals"]["s3:RequestObjectTagKeys"].([]any)
100 for _, k := range keys {
101 if k == TagVerdict {
102 deniesHeader = true
103 }
104 }
105 }
106 if !deniesCall {
107 t.Fatalf("nothing denies the PutObjectTagging call:\n%s", doc)
108 }
109 if !deniesHeader {
110 // The one a reviewer will not miss twice.
111 t.Fatalf("nothing denies x-amz-tagging carrying %s on upload — an app can ship a payload pre-marked clean:\n%s", TagVerdict, doc)
112 }
113}
114
115// The fake rewrites an EXISTING user's policy. MemProvisioner.EnsureUser
116// early-returns when the principal is already there, and enforcement is only
117// ever turned on for a store that already has one — so a fake that recorded
118// the policy on the create branch alone would make the next test vacuous.
119func TestMemProvisionerRewritesPolicyForExistingUser(t *testing.T) {
120 ctx := context.Background()
121 p := NewMemProvisioner("eu-west-1")
122
123 if _, err := p.EnsureUser(ctx, "u", UserPath, "arn:aws:s3:::b", nil); err != nil {
124 t.Fatal(err)
125 }
126 // The ENFORCEMENT deny, specifically: the anti-forgery Denys are in every
127 // policy now, so `"Effect":"Deny"` no longer tells the two states apart.
128 if strings.Contains(p.PolicyFor("u"), "s3:ExistingObjectTag") {
129 t.Fatal("no enforcement deny was asked for")
130 }
131 if _, err := p.EnsureUser(ctx, "u", UserPath, "arn:aws:s3:::b",
132 &DenyTag{Key: TagVerdict, Value: string(VerdictInfected)}); err != nil {
133 t.Fatal(err)
134 }
135 if !strings.Contains(p.PolicyFor("u"), "s3:ExistingObjectTag") {
136 t.Fatal("EnsureUser did not rewrite an existing user's policy")
137 }
138}
139
140// Flipping enforcement on a HEALTHY, fully-delivered store must reach the
141// backend.
142//
143// Before EnsureUser was hoisted it could not: EnsureUser is the only
144// PutUserPolicy caller, Grant returned at the idempotence short-circuit before
145// ever reaching it, and Rotate never writes a policy at all — so enforcement
146// would have been permanently inert on every store it was ever turned on for.
147// This is the same bug internal/mail's walk fixed as "C2".
148func TestGrantWritesDenyOnHealthyStore(t *testing.T) {
149 ctx := context.Background()
150 s := release.NewDirStore(t.TempDir())
151 _, pub := testSealKeypair(t)
152 p := NewMemProvisioner("eu-west-1")
153
154 // rotateFixture leaves a fully provisioned, fully delivered store — the
155 // exact state the short-circuit returns early on.
156 rotateFixture(t, s, pub, p)
157 user := UserName("bab", "titogo")
158 // The ENFORCEMENT deny: every policy now carries the two anti-forgery
159 // Denys, so the presence of a Deny no longer means enforcement is on.
160 if strings.Contains(p.PolicyFor(user), "s3:ExistingObjectTag") {
161 t.Fatal("no enforcement deny was asked for yet")
162 }
163
164 // Turn enforcement on. Read-modify-write with the real etag: Put with ""
165 // is create-only and would return release.ErrCASConflict here.
166 stored, etag, err := Get(ctx, s, "bab", "titogo")
167 if err != nil || stored == nil {
168 t.Fatalf("re-read: %+v, %v", stored, err)
169 }
170 stored.Scanning = &ScanBlock{
171 Enabled: &ScanEnabled{Engine: "clamav", At: time.Now().UTC(), By: "op@example.com"},
172 Enforce: &DenyTag{Key: TagVerdict, Value: string(VerdictInfected)},
173 }
174 if _, err := Put(ctx, s, "bab", "titogo", stored, etag); err != nil {
175 t.Fatal(err)
176 }
177
178 if _, err := Grant(ctx, s, p, testDeployment(), pub,
179 "bab", "titogo", "op@example.com", GrantSourceOperator, "", time.Now().UTC()); err != nil {
180 t.Fatal(err)
181 }
182 // s3:ExistingObjectTag, not `"Effect":"Deny"`: every policy carries the two
183 // anti-forgery Denys now, so the looser check would pass with the hoist
184 // entirely broken — a green test asserting nothing.
185 if !strings.Contains(p.PolicyFor(user), "s3:ExistingObjectTag") {
186 t.Fatalf("re-walking a healthy store did not write the enforcement deny — the hoist is not working:\n%s",
187 p.PolicyFor(user))
188 }
189}
190
191// The mirror: clearing Enforce and re-walking takes the deny back out. A
192// `scan enforce off` that left a live Deny standing would be worse than never
193// having offered the switch.
194func TestGrantClearsDenyWhenEnforceIsCleared(t *testing.T) {
195 ctx := context.Background()
196 s := release.NewDirStore(t.TempDir())
197 _, pub := testSealKeypair(t)
198 p := NewMemProvisioner("eu-west-1")
199 rotateFixture(t, s, pub, p)
200 user := UserName("bab", "titogo")
201
202 stored, etag, err := Get(ctx, s, "bab", "titogo")
203 if err != nil {
204 t.Fatal(err)
205 }
206 stored.Scanning = &ScanBlock{Enforce: &DenyTag{Key: TagVerdict, Value: string(VerdictInfected)}}
207 if _, err := Put(ctx, s, "bab", "titogo", stored, etag); err != nil {
208 t.Fatal(err)
209 }
210 if _, err := Grant(ctx, s, p, testDeployment(), pub,
211 "bab", "titogo", "op@example.com", GrantSourceOperator, "", time.Now().UTC()); err != nil {
212 t.Fatal(err)
213 }
214 if !strings.Contains(p.PolicyFor(user), "s3:ExistingObjectTag") {
215 t.Fatal("setup: the enforcement deny was never written")
216 }
217
218 stored, etag, err = Get(ctx, s, "bab", "titogo")
219 if err != nil {
220 t.Fatal(err)
221 }
222 stored.Scanning.Enforce = nil
223 if _, err := Put(ctx, s, "bab", "titogo", stored, etag); err != nil {
224 t.Fatal(err)
225 }
226 if _, err := Grant(ctx, s, p, testDeployment(), pub,
227 "bab", "titogo", "op@example.com", GrantSourceOperator, "", time.Now().UTC()); err != nil {
228 t.Fatal(err)
229 }
230 if strings.Contains(p.PolicyFor(user), "s3:ExistingObjectTag") {
231 t.Fatalf("clearing Enforce left a live enforcement Deny standing:\n%s", p.PolicyFor(user))
232 }
233 // And clearing enforcement must NOT have taken the anti-forgery Denys with
234 // it — they are unconditional, and a store that stops enforcing is exactly
235 // where a forged clean tag would go unnoticed longest.
236 assertNoPutObjectTagging(t, p.PolicyFor(user))
237}
238