carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "net/http"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13// staticAddresses is a fixed AddressSource.
14type staticAddresses BoxAddresses
15
16func (a staticAddresses) BoxAddresses() (BoxAddresses, bool) { return BoxAddresses(a), true }
17
18// countingAddresses counts reads, answering each from its list in turn. A
19// read whose index is in unknown reports that the source could not tell.
20type countingAddresses struct {
21 answers []BoxAddresses
22 unknown map[int]bool
23 reads int
24}
25
26func (c *countingAddresses) BoxAddresses() (BoxAddresses, bool) {
27 i := c.reads
28 c.reads++
29 if c.unknown[i] {
30 return BoxAddresses{}, false
31 }
32 return c.answers[min(i, len(c.answers)-1)], true
33}
34
35// addrs is the answer of a source that is known to have answered.
36func addrs(s AddressSource) BoxAddresses {
37 a, ok := s.BoxAddresses()
38 if !ok {
39 panic("address source did not answer")
40 }
41 return a
42}
43
44func envOf(m map[string]string) func(string) string { return func(k string) string { return m[k] } }
45
46// The core source works with no provider adapter at all (§25).
47func TestEnvAddressesWithoutAProvider(t *testing.T) {
48 got := addrs(envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": " 10.0.1.5 ", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16", "CARLOS_PUBLIC_IP": "198.51.100.7"})})
49 if got != (BoxAddresses{Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "198.51.100.7"}) {
50 t.Fatalf("got %+v", got)
51 }
52 if got := addrs((envAddresses{getenv: envOf(nil)})); got != (BoxAddresses{}) {
53 t.Fatalf("nothing set: %+v", got)
54 }
55 // CARLOS_PRIVATE_IP needs CARLOS_PRIVATE_CIDR beside it.
56 if got := addrs((envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "10.0.1.5"})})); got.Private != "" {
57 t.Fatalf("a private address with no network: %+v", got)
58 }
59 got = addrs(envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "169.254.169.254", "CARLOS_PRIVATE_CIDR": "169.254.0.0/16", "CARLOS_PUBLIC_IP": "box.example.com"})})
60 if got != (BoxAddresses{}) {
61 t.Fatalf("invalid settings: %+v", got)
62 }
63}
64
65func TestEnvAddressesOverrideTheProviderFieldByField(t *testing.T) {
66 provider := &countingAddresses{answers: []BoxAddresses{{Private: "10.0.9.9", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.9"}}}
67 got := addrs(envAddresses{getenv: envOf(map[string]string{"CARLOS_PUBLIC_IP": "198.51.100.7"}), provider: provider})
68 if got != (BoxAddresses{Private: "10.0.9.9", PrivateCIDR: "10.0.0.0/16", Public: "198.51.100.7"}) {
69 t.Fatalf("got %+v", got)
70 }
71 // Both set: the provider is not asked.
72 provider.reads = 0
73 addrs(envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "10.0.1.5", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16", "CARLOS_PUBLIC_IP": "198.51.100.7"}), provider: provider})
74 if provider.reads != 0 {
75 t.Fatalf("provider asked %d times with both settings present", provider.reads)
76 }
77 // A bad private setting publishes no private address; the provider's
78 // answer does not replace it.
79 got = addrs(envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_IP": "not-an-ip", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16"}), provider: provider})
80 if got != (BoxAddresses{Public: "203.0.113.9"}) {
81 t.Fatalf("bad setting: %+v", got)
82 }
83 // The provider's answer is validated too.
84 bad := &countingAddresses{answers: []BoxAddresses{{Private: "10.9.9.9", PrivateCIDR: "10.0.0.0/16"}}}
85 if got := addrs((envAddresses{getenv: envOf(nil), provider: bad})); got.Private != "" {
86 t.Fatalf("an out-of-network provider address: %+v", got)
87 }
88}
89
90func TestIMDSAddressesReadsTheLeavesAndTheVPCNetwork(t *testing.T) {
91 leaves := map[string]string{
92 "/latest/meta-data/local-ipv4": "10.0.1.5",
93 "/latest/meta-data/public-ipv4": "203.0.113.4",
94 "/latest/meta-data/mac": "0e:12:34:56:78:9a",
95 "/latest/meta-data/network/interfaces/macs/0e:12:34:56:78:9a/vpc-ipv4-cidr-blocks": "172.31.0.0/16\n10.0.0.0/16",
96 }
97 s := imdsAddresses{token: func() string { return "tok" }, leaf: func(token, path string) (string, int) {
98 if token != "tok" {
99 t.Fatalf("token %q", token)
100 }
101 v, ok := leaves[path]
102 if !ok {
103 return "", http.StatusNotFound
104 }
105 return v, http.StatusOK
106 }}
107 if got := addrs(s); got != (BoxAddresses{Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.4"}) {
108 t.Fatalf("got %+v", got)
109 }
110 // An address outside the VPC's blocks is not published.
111 leaves["/latest/meta-data/local-ipv4"] = "10.9.1.5"
112 if got := addrs(s); got.Private != "" || got.PrivateCIDR != "" {
113 t.Fatalf("out of network: %+v", got)
114 }
115 // No VPC network answer: no private address.
116 leaves["/latest/meta-data/local-ipv4"] = "10.0.1.5"
117 delete(leaves, "/latest/meta-data/network/interfaces/macs/0e:12:34:56:78:9a/vpc-ipv4-cidr-blocks")
118 if got := addrs(s); got.Private != "" {
119 t.Fatalf("no network: %+v", got)
120 }
121 // A 404 on public-ipv4 is IMDS saying this instance holds no public
122 // address (the EIP gone to the load balancer, A1-12): an authoritative
123 // blank, published as none.
124 delete(leaves, "/latest/meta-data/public-ipv4")
125 if got := addrs(s); got.Public != "" {
126 t.Fatalf("no public leaf: %+v", got)
127 }
128 noToken := imdsAddresses{token: func() string { return "" }, leaf: s.leaf}
129 if got, ok := noToken.BoxAddresses(); ok || got != (BoxAddresses{}) {
130 t.Fatalf("no token: %+v, ok %v; want unknown", got, ok)
131 }
132}
133
134func readEdgesFile(t *testing.T, s release.Store, box string) edgesFile {
135 t.Helper()
136 body, _, err := s.Get(context.Background(), edgesFileKey(box))
137 if err != nil {
138 t.Fatal(err)
139 }
140 var f edgesFile
141 if err := json.Unmarshal(body, &f); err != nil {
142 t.Fatal(err)
143 }
144 return f
145}
146
147// Region HA spec 2 §6 (A1-12): with region HA on, every publication reads
148// the address source afresh, and a blank read publishes no address. The
149// last published EIP is never held: it may be the region's, which the
150// load balancer has taken.
151func TestEdgesFilePublishesOnlyWhatTheSourceReports(t *testing.T) {
152 s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
153 seedRoute(t, reg, "app.example.com")
154 claims := newEdgesClaims("annie", (&claimsBook{}).read)
155 src := &countingAddresses{answers: []BoxAddresses{
156 {Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.9"},
157 {Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16"},
158 }}
159 claims.addrs = src
160 var last []byte
161 ident := edgesFile{Box: "annie", BoxID: "annie"}
162 // The loop's own reading (the eip argument) is not what is published.
163 if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "198.51.100.1", &last, false, claims); err != nil {
164 t.Fatal(err)
165 }
166 if f := readEdgesFile(t, s, "annie"); f.EIP != "203.0.113.9" || f.PrivateIP != "10.0.1.5" || f.PrivateCIDR != "10.0.0.0/16" {
167 t.Fatalf("first publication: %+v", f)
168 }
169 if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, false, claims); err != nil {
170 t.Fatal(err)
171 }
172 if f := readEdgesFile(t, s, "annie"); f.EIP != "" || f.PrivateIP != "10.0.1.5" {
173 t.Fatalf("after a blank public read: eip %q private %q, want no public address", f.EIP, f.PrivateIP)
174 }
175 if src.reads != 2 {
176 t.Fatalf("source read %d times, want once per publication", src.reads)
177 }
178}
179
180// With region HA off there is no claims cache, and the publisher is
181// main's: no private address, and a blank probe holds the last EIP.
182func TestEdgesFileWithRegionHAOffKeepsTheEIPHold(t *testing.T) {
183 s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
184 seedRoute(t, reg, "app.example.com")
185 var last []byte
186 ident := edgesFile{Box: "annie"}
187 if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "203.0.113.9", &last, false, nil); err != nil {
188 t.Fatal(err)
189 }
190 if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, true, nil); err != nil {
191 t.Fatal(err)
192 }
193 if f := readEdgesFile(t, s, "annie"); f.EIP != "203.0.113.9" || f.PrivateIP != "" || f.PrivateCIDR != "" {
194 t.Fatalf("%+v", f)
195 }
196}
197
198// Anything but a 200 or a 404 from a leaf, and a failed token, is unknown,
199// not blank (the census-fix ruling): a hiccup must not read as "no address".
200func TestIMDSAddressesUnknownIsNotBlank(t *testing.T) {
201 leaves := map[string]string{
202 "/latest/meta-data/local-ipv4": "10.0.1.5",
203 "/latest/meta-data/public-ipv4": "203.0.113.4",
204 "/latest/meta-data/mac": "0e:12:34:56:78:9a",
205 "/latest/meta-data/network/interfaces/macs/0e:12:34:56:78:9a/vpc-ipv4-cidr-blocks": "10.0.0.0/16",
206 }
207 for _, bad := range []struct {
208 name, path string
209 code int
210 }{
211 {"public-ipv4 times out", "/latest/meta-data/public-ipv4", 0},
212 {"public-ipv4 5xx", "/latest/meta-data/public-ipv4", http.StatusServiceUnavailable},
213 {"local-ipv4 401", "/latest/meta-data/local-ipv4", http.StatusUnauthorized},
214 {"mac fails", "/latest/meta-data/mac", 0},
215 {"vpc blocks 500", "/latest/meta-data/network/interfaces/macs/0e:12:34:56:78:9a/vpc-ipv4-cidr-blocks", http.StatusInternalServerError},
216 } {
217 t.Run(bad.name, func(t *testing.T) {
218 s := imdsAddresses{token: func() string { return "tok" }, leaf: func(token, path string) (string, int) {
219 if path == bad.path {
220 return "", bad.code
221 }
222 return leaves[path], http.StatusOK
223 }}
224 if got, ok := s.BoxAddresses(); ok || got != (BoxAddresses{}) {
225 t.Fatalf("got %+v, ok %v; want unknown", got, ok)
226 }
227 })
228 }
229 // A malformed mac never reaches a request path.
230 s := imdsAddresses{token: func() string { return "tok" }, leaf: func(token, path string) (string, int) {
231 if path == "/latest/meta-data/mac" {
232 return "../../x", http.StatusOK
233 }
234 return leaves[path], http.StatusOK
235 }}
236 if got, ok := s.BoxAddresses(); !ok || got.Private != "" || got.Public != "203.0.113.4" {
237 t.Fatalf("bad mac: %+v, ok %v", got, ok)
238 }
239}
240
241// An unknown provider read is unknown to the whole source, unless host.env
242// answered every field.
243func TestEnvAddressesPassUnknownThrough(t *testing.T) {
244 provider := &countingAddresses{unknown: map[int]bool{0: true, 1: true}}
245 if got, ok := (envAddresses{getenv: envOf(map[string]string{"CARLOS_PUBLIC_IP": "198.51.100.7"}), provider: provider}).BoxAddresses(); ok || got != (BoxAddresses{}) {
246 t.Fatalf("got %+v, ok %v; want unknown", got, ok)
247 }
248 full := map[string]string{"CARLOS_PRIVATE_IP": "10.0.1.5", "CARLOS_PRIVATE_CIDR": "10.0.0.0/16", "CARLOS_PUBLIC_IP": "198.51.100.7"}
249 if _, ok := (envAddresses{getenv: envOf(full), provider: provider}).BoxAddresses(); !ok {
250 t.Fatal("host.env answered every field; the provider was not needed")
251 }
252}
253
254// CARLOS_PRIVATE_CIDR alone is not an address.
255func TestEnvAddressesCIDRWithoutIP(t *testing.T) {
256 got := addrs(envAddresses{getenv: envOf(map[string]string{"CARLOS_PRIVATE_CIDR": "10.0.0.0/16", "CARLOS_PUBLIC_IP": "198.51.100.7"})})
257 if got != (BoxAddresses{Public: "198.51.100.7"}) {
258 t.Fatalf("got %+v", got)
259 }
260}
261
262// An unknown read (a metadata hiccup) writes nothing: the previous file
263// stands, the pass reports an error so the loop retries, and a later good
264// read publishes again.
265func TestEdgesFileUnknownAddressesLeaveThePublishedFileStanding(t *testing.T) {
266 s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
267 seedRoute(t, reg, "app.example.com")
268 claims := newEdgesClaims("annie", (&claimsBook{}).read)
269 src := &countingAddresses{
270 answers: []BoxAddresses{
271 {Private: "10.0.1.5", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.9"},
272 {},
273 {Private: "10.0.1.6", PrivateCIDR: "10.0.0.0/16", Public: "203.0.113.9"},
274 },
275 unknown: map[int]bool{1: true},
276 }
277 claims.addrs = src
278 var last []byte
279 ident := edgesFile{Box: "annie", BoxID: "annie"}
280 if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, false, claims); err != nil {
281 t.Fatal(err)
282 }
283 before, _, err := s.Get(context.Background(), edgesFileKey("annie"))
284 if err != nil {
285 t.Fatal(err)
286 }
287 lastBefore := string(last)
288 // A forced timed pass, which would rewrite an unchanged body.
289 err = edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, true, claims)
290 if !errors.Is(err, errBoxAddressesUnknown) {
291 t.Fatalf("unknown read: err %v, want errBoxAddressesUnknown", err)
292 }
293 after, _, err := s.Get(context.Background(), edgesFileKey("annie"))
294 if err != nil {
295 t.Fatal(err)
296 }
297 if string(after) != string(before) || string(last) != lastBefore {
298 t.Fatalf("an unknown read changed the published file:\nbefore %s\nafter %s", before, after)
299 }
300 if f := readEdgesFile(t, s, "annie"); f.EIP != "203.0.113.9" || f.PrivateIP != "10.0.1.5" {
301 t.Fatalf("published file lost its addresses: %+v", f)
302 }
303 // The retry reads again and publishes what it now reports.
304 if err := edgesFileWriteClaims(context.Background(), s, reg, ident, "", &last, true, claims); err != nil {
305 t.Fatal(err)
306 }
307 if f := readEdgesFile(t, s, "annie"); f.PrivateIP != "10.0.1.6" {
308 t.Fatalf("after the retry: %+v", f)
309 }
310 if src.reads != 3 {
311 t.Fatalf("source read %d times, want once per attempt", src.reads)
312 }
313}
314