package status import ( "context" "io" "net/http" "strings" "time" ) // IMDSBase is the link-local instance metadata service every EC2 machine // answers on. It is a var only so the tests can point the reader at an // httptest server; nothing else reassigns it. var IMDSBase = "http://169.254.169.254" // imdsTimeout bounds the whole two-request exchange. It is deliberately // short: off-cloud the address is unroutable and the only honest answer is // "no instance id", which must not cost the status loop its tick. The // link-local service answers in single-digit milliseconds when it answers // at all. const imdsTimeout = 2 * time.Second // InstanceID is the id of the EC2 instance this process runs on, or "" for // anything that is not one — a Pi on a desk, a laptop, a container, another // cloud. // // It exists because the console's architecture page has to answer "which // machine is this box?", and every other way of asking is a naming // convention that can drift: the flagship's instances are tagged // carlos-flagship-ie while its boxes call themselves annie-welding, so a // Name-tag match found nothing at all. The box is the only thing that knows // which machine it is without being told, and this is it asking. // // The empty string is a real answer, not a failure: it is what tells the // console this is somebody else's hardware rather than a machine it looked // for and missed. func InstanceID() string { return instanceIDFrom(IMDSBase, imdsTimeout) } // instanceIDFrom speaks IMDSv2 — a PUT for a short-lived token, then a GET // carrying it. IMDSv1 (a bare GET) is not attempted: every box this // platform provisions has v2 enforced, and falling back would only make an // unreachable service take twice as long to say nothing. func instanceIDFrom(base string, timeout time.Duration) string { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() client := &http.Client{Timeout: timeout} req, err := http.NewRequestWithContext(ctx, http.MethodPut, base+"/latest/api/token", nil) if err != nil { return "" } req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "60") resp, err := client.Do(req) if err != nil { return "" } token, err := io.ReadAll(io.LimitReader(resp.Body, 4096)) resp.Body.Close() if err != nil || resp.StatusCode != http.StatusOK || len(token) == 0 { return "" } req, err = http.NewRequestWithContext(ctx, http.MethodGet, base+"/latest/meta-data/instance-id", nil) if err != nil { return "" } req.Header.Set("X-aws-ec2-metadata-token", string(token)) resp, err = client.Do(req) if err != nil { return "" } body, err := io.ReadAll(io.LimitReader(resp.Body, 4096)) resp.Body.Close() if err != nil || resp.StatusCode != http.StatusOK { return "" } // Trimmed because a stray newline would make the console's join miss by // one byte, which is the most annoying possible way for this to fail. return strings.TrimSpace(string(body)) }