carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "bytes"
5 "context"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "io"
11 "os"
12 "strings"
13 "testing"
14 "time"
15
16 "github.com/carlosframework/platform/internal/geoip"
17 "github.com/carlosframework/platform/internal/release"
18)
19
20func geoipTestBlob(t *testing.T) (key string, body []byte) {
21 t.Helper()
22 body = bytes.Repeat([]byte("geoip-blob"), 100)
23 sum := sha256.Sum256(body)
24 return geoip.BlobKey(hex.EncodeToString(sum[:])), body
25}
26
27func geoipTestPointer(t *testing.T, key string, body []byte) []byte {
28 t.Helper()
29 sum := sha256.Sum256(body)
30 sha := hex.EncodeToString(sum[:])
31 p := geoip.Pointer{V: 1, SHA256: sha, Key: geoip.BlobKey(sha), Size: int64(len(body)), DatabaseType: "DBIP-City-Lite",
32 BuildEpoch: 1, Published: time.Unix(2, 0).UTC(), Origin: "manual", Edition: "test"}
33 b, err := json.MarshalIndent(p, "", " ")
34 if err != nil {
35 t.Fatal(err)
36 }
37 return b
38}
39
40func TestClassifyGeoipIsExact(t *testing.T) {
41 ctx := context.Background()
42 key, body := geoipTestBlob(t)
43 ptr := geoipTestPointer(t, key, body)
44 feed := []byte(`{"v":1,"feed":"dbip-city-lite"}`)
45 for _, c := range []struct {
46 name string
47 target MutationTarget
48 family string
49 }{
50 {"pointer put", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.PointerKey, After: ptr}, "geoip-pointer"},
51 {"pointer delete", MutationTarget{Surface: "bucket", Method: "Delete", Key: geoip.PointerKey, Before: ptr}, "geoip-pointer"},
52 {"pointer delete of an absent key", MutationTarget{Surface: "bucket", Method: "Delete", Key: geoip.PointerKey}, "geoip-pointer"},
53 {"feed put", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.FeedKey, After: feed}, "geoip-feed"},
54 {"feed delete", MutationTarget{Surface: "bucket", Method: "Delete", Key: geoip.FeedKey}, "geoip-feed"},
55 {"blob put (buffered)", MutationTarget{Surface: "bucket", Method: "Put", Key: key, After: body}, "geoip-blob"},
56 {"blob put (streamed, no body)", MutationTarget{Surface: "bucket", Method: "Put", Key: key}, "geoip-blob"},
57 {"blob delete", MutationTarget{Surface: "bucket", Method: "Delete", Key: key}, "geoip-blob"},
58 {"blob delete if match", MutationTarget{Surface: "bucket", Method: "DeleteIfMatch", Key: key, ETag: "x"}, "geoip-blob"},
59 } {
60 got, err := ClassifyMutation(ctx, nil, nil, c.target)
61 if err != nil {
62 t.Errorf("%s: %v", c.name, err)
63 continue
64 }
65 if got.Family != c.family || got.NonApp == "" || len(got.Scopes) != 0 || got.MailGate {
66 t.Errorf("%s: classified %+v, want non-app family %s with no scopes", c.name, got, c.family)
67 }
68 }
69 for _, c := range []struct {
70 name string
71 target MutationTarget
72 }{
73 {"pointer put that is not a pointer", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.PointerKey, After: []byte(`{"v":1}`)}},
74 {"pointer put with no body", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.PointerKey}},
75 {"feed put that is not a status", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.FeedKey, After: []byte(`nope`)}},
76 {"blob put whose body is not its name", MutationTarget{Surface: "bucket", Method: "Put", Key: key, After: []byte("other")}},
77 {"blob with a short name", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.Prefix + "abc.mmdb"}},
78 {"blob with an upper-case name", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.Prefix + strings.ToUpper(strings.TrimSuffix(strings.TrimPrefix(key, geoip.Prefix), ".mmdb")) + ".mmdb"}},
79 {"nested key", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.Prefix + "x/current.json"}},
80 {"prefix itself", MutationTarget{Surface: "bucket", Method: "Delete", Key: geoip.Prefix}},
81 {"a stray file", MutationTarget{Surface: "bucket", Method: "Put", Key: geoip.Prefix + "notes.txt", After: []byte("x")}},
82 {"another store under a lookalike prefix", MutationTarget{Surface: "bucket", Method: "Put", Key: "control/geoip-other/current.json", After: ptr}},
83 } {
84 if got, err := ClassifyMutation(context.Background(), nil, nil, c.target); !errors.Is(err, ErrUnclassified) {
85 t.Errorf("%s: classified %+v, err %v; want ErrUnclassified", c.name, got, err)
86 }
87 }
88}
89
90// PutFrom must reach the raw store only through the classifier, only for an
91// exact blob key, and never buffer a blob.
92func TestMutationBoundaryPutFromIsClassified(t *testing.T) {
93 ctx := context.Background()
94 raw := release.NewDirStore(t.TempDir())
95 var seen []MutationTarget
96 check := func(ctx context.Context, tg MutationTarget) error {
97 seen = append(seen, tg)
98 _, err := ClassifyMutation(ctx, raw, nil, tg)
99 return err
100 }
101 b, err := NewMutationBoundary(true, raw, check)
102 if err != nil {
103 t.Fatal(err)
104 }
105 ps, ok := b.(release.PutStreamer)
106 if !ok {
107 t.Fatalf("%T is not a PutStreamer: PutObjectFrom would buffer a GeoIP blob through Put", b)
108 }
109 key, body := geoipTestBlob(t)
110 if _, err := ps.PutFrom(ctx, key, bytes.NewReader(body), int64(len(body)), ""); err != nil {
111 t.Fatalf("blob PutFrom: %v", err)
112 }
113 if len(seen) != 1 || seen[0].Key != key || seen[0].Method != "Put" {
114 t.Fatalf("checker saw %+v, want exactly one Put of %s", seen, key)
115 }
116 if got, _, err := raw.Get(ctx, key); err != nil || !bytes.Equal(got, body) {
117 t.Fatalf("blob not stored: %v", err)
118 }
119 // Create-only: a second put of the same name conflicts in the raw store.
120 if _, err := ps.PutFrom(ctx, key, bytes.NewReader(body), int64(len(body)), ""); !errors.Is(err, release.ErrCASConflict) {
121 t.Errorf("second blob PutFrom: %v, want ErrCASConflict", err)
122 }
123 // A conditional streamed put is not the blob path: it takes the checked,
124 // buffered Put, which classifies the pointer body it was given.
125 seen = nil
126 ptr := geoipTestPointer(t, key, body)
127 if _, err := ps.PutFrom(ctx, geoip.PointerKey, bytes.NewReader(ptr), int64(len(ptr)), ""); err != nil {
128 t.Fatalf("pointer PutFrom: %v", err)
129 }
130 if len(seen) != 1 || seen[0].Key != geoip.PointerKey || !bytes.Equal(seen[0].After, ptr) {
131 t.Errorf("checker saw %+v for a pointer written through PutFrom, want its body", seen)
132 }
133 // An unclassified key never reaches the raw store, however it is spelled.
134 for _, k := range []string{geoip.Prefix + "stray.mmdb", "control/geoip/x/" + strings.Repeat("a", 64) + ".mmdb", "status/other.json"} {
135 seen = nil
136 if _, err := ps.PutFrom(ctx, k, strings.NewReader("x"), 1, ""); err == nil {
137 t.Errorf("PutFrom %s succeeded through the boundary", k)
138 }
139 if _, _, err := raw.Get(ctx, k); !errors.Is(err, os.ErrNotExist) {
140 t.Errorf("PutFrom %s left an object in the raw store: %v", k, err)
141 }
142 }
143 // A blob larger than the pointer allows is refused before any effect.
144 if _, err := ps.PutFrom(ctx, key, strings.NewReader(""), geoip.MaxBlobBytes+1, ""); err == nil {
145 t.Error("an over-limit blob size was accepted")
146 }
147}
148
149// Production has two wrappers between the publisher and the streaming
150// store. Neither may turn a large deployment blob into a buffered Put.
151type geoipStreamProbe struct {
152 release.Store
153 streamed bool
154}
155
156func (p *geoipStreamProbe) PutFrom(_ context.Context, _ string, _ io.ReadSeeker, _ int64, _ string) (string, error) {
157 p.streamed = true
158 return "streamed", nil
159}
160func TestGeoipStreamingSurvivesRecordBoundary(t *testing.T) {
161 raw := &geoipStreamProbe{Store: release.NewDirStore(t.TempDir())}
162 records, err := NewConditionalRecordStore(raw, make([]byte, 32))
163 if err != nil {
164 t.Fatal(err)
165 }
166 guarded, err := NewMutationBoundary(true, records, func(ctx context.Context, tg MutationTarget) error {
167 _, err := ClassifyMutation(ctx, records, nil, tg)
168 return err
169 })
170 if err != nil {
171 t.Fatal(err)
172 }
173 key, _ := geoipTestBlob(t)
174 if _, err = release.PutObjectFrom(t.Context(), guarded, key, bytes.NewReader(nil), release.PutFromFallbackMax+1, ""); err != nil || !raw.streamed {
175 t.Fatalf("streaming lost: %v, raw=%v", err, raw.streamed)
176 }
177 raw.streamed = false
178 if _, err = release.PutObjectFrom(t.Context(), guarded, "apps/bab/demo/data", bytes.NewReader(nil), release.PutFromFallbackMax+1, ""); err == nil || raw.streamed {
179 t.Fatalf("app bypass: %v, raw=%v", err, raw.streamed)
180 }
181}
182