package host import ( "net" "net/http" "strings" "time" "github.com/carlosframework/platform/internal/edgesfile" ) // imdsAddresses is the AWS adapter of AddressSource (region HA spec 2 // ยง25): IMDSv2's local-ipv4, public-ipv4, and the VPC's CIDR blocks for // the primary interface (network/interfaces/macs//vpc-ipv4-cidr-blocks), // read on every call. IMDS reports only addresses this instance holds, so // once the region's EIP has moved to the load balancer no box reports it // (A1-12). A leaf that does not answer 200 is blank. type imdsAddresses struct { token func() string leaf func(token, path string) (string, int) } func newIMDSAddresses() imdsAddresses { client := &http.Client{Timeout: 2 * time.Second} return imdsAddresses{ token: func() string { return imdsToken(client) }, leaf: func(token, path string) (string, int) { return imdsLeaf(client, token, path) }, } } func (s imdsAddresses) BoxAddresses() (BoxAddresses, bool) { token := s.token() if token == "" { return BoxAddresses{}, false } // read is a leaf's answer: its value on 200, blank on 404 (IMDS says the // instance has no such leaf: authoritative), and unknown on anything // else, a failed or timed-out request included. unknown := false read := func(path string) string { v, code := s.leaf(token, path) switch code { case http.StatusOK: return v case http.StatusNotFound: default: unknown = true } return "" } out := BoxAddresses{Public: read("/latest/meta-data/public-ipv4")} ip := read("/latest/meta-data/local-ipv4") mac := read("/latest/meta-data/mac") if unknown { return BoxAddresses{}, false } if ip == "" || mac == "" { return out, true } if _, err := net.ParseMAC(mac); err != nil { return out, true } blocks := strings.Fields(read("/latest/meta-data/network/interfaces/macs/" + mac + "/vpc-ipv4-cidr-blocks")) if unknown { return BoxAddresses{}, false } out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(ip, blocks) return out, true } // boxAddressSource is what the edges file publisher reads with region HA // on: host.env's settings, then IMDS on EC2. IMDS is asked on every // publication that host.env does not fully answer; a failed request makes // that publication unknown, and the pass writes nothing and retries. func boxAddressSource(getenv func(string) string) AddressSource { return envAddresses{getenv: getenv, provider: newIMDSAddresses()} }