| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "net" |
| 5 | "net/http" |
| 6 | "strings" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/edgesfile" |
| 10 | ) |
| 11 | |
| 12 | // imdsAddresses is the AWS adapter of AddressSource (region HA spec 2 |
| 13 | // §25): IMDSv2's local-ipv4, public-ipv4, and the VPC's CIDR blocks for |
| 14 | // the primary interface (network/interfaces/macs/<mac>/vpc-ipv4-cidr-blocks), |
| 15 | // read on every call. IMDS reports only addresses this instance holds, so |
| 16 | // once the region's EIP has moved to the load balancer no box reports it |
| 17 | // (A1-12). A leaf that does not answer 200 is blank. |
| 18 | type imdsAddresses struct { |
| 19 | token func() string |
| 20 | leaf func(token, path string) (string, int) |
| 21 | } |
| 22 | |
| 23 | func newIMDSAddresses() imdsAddresses { |
| 24 | client := &http.Client{Timeout: 2 * time.Second} |
| 25 | return imdsAddresses{ |
| 26 | token: func() string { return imdsToken(client) }, |
| 27 | leaf: func(token, path string) (string, int) { return imdsLeaf(client, token, path) }, |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | func (s imdsAddresses) BoxAddresses() (BoxAddresses, bool) { |
| 32 | token := s.token() |
| 33 | if token == "" { |
| 34 | return BoxAddresses{}, false |
| 35 | } |
| 36 | // read is a leaf's answer: its value on 200, blank on 404 (IMDS says the |
| 37 | // instance has no such leaf: authoritative), and unknown on anything |
| 38 | // else, a failed or timed-out request included. |
| 39 | unknown := false |
| 40 | read := func(path string) string { |
| 41 | v, code := s.leaf(token, path) |
| 42 | switch code { |
| 43 | case http.StatusOK: |
| 44 | return v |
| 45 | case http.StatusNotFound: |
| 46 | default: |
| 47 | unknown = true |
| 48 | } |
| 49 | return "" |
| 50 | } |
| 51 | out := BoxAddresses{Public: read("/latest/meta-data/public-ipv4")} |
| 52 | ip := read("/latest/meta-data/local-ipv4") |
| 53 | mac := read("/latest/meta-data/mac") |
| 54 | if unknown { |
| 55 | return BoxAddresses{}, false |
| 56 | } |
| 57 | if ip == "" || mac == "" { |
| 58 | return out, true |
| 59 | } |
| 60 | if _, err := net.ParseMAC(mac); err != nil { |
| 61 | return out, true |
| 62 | } |
| 63 | blocks := strings.Fields(read("/latest/meta-data/network/interfaces/macs/" + mac + "/vpc-ipv4-cidr-blocks")) |
| 64 | if unknown { |
| 65 | return BoxAddresses{}, false |
| 66 | } |
| 67 | out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(ip, blocks) |
| 68 | return out, true |
| 69 | } |
| 70 | |
| 71 | // boxAddressSource is what the edges file publisher reads with region HA |
| 72 | // on: host.env's settings, then IMDS on EC2. IMDS is asked on every |
| 73 | // publication that host.env does not fully answer; a failed request makes |
| 74 | // that publication unknown, and the pass writes nothing and retries. |
| 75 | func boxAddressSource(getenv func(string) string) AddressSource { |
| 76 | return envAddresses{getenv: getenv, provider: newIMDSAddresses()} |
| 77 | } |
| 78 | |