JuliusBrussee/caveman · error

awscreds: build imds token request

Error message

awscreds: build imds token request: %w

What it means

fromIMDS wraps an error from http.NewRequestWithContext while building the IMDSv2 PUT request to /latest/api/token. The library throws this because the resolved IMDS base URL could not be turned into a request, so no session token can be fetched.

Solutions

  1. Validate AWS_EC2_METADATA_SERVICE_ENDPOINT is a clean absolute http(s) URL.
  2. Ensure the context passed to the provider has not already been canceled; check the wrapped cause for 'context canceled' vs 'net/url' parse errors.
  3. Unset the endpoint env var to use the default IMDS address.
  4. Trim whitespace/newlines when setting the env var programmatically (strings.TrimSpace).

Example fix

// before
os.Setenv("AWS_EC2_METADATA_SERVICE_ENDPOINT", "http://169.254.169.254\n")
// after
os.Setenv("AWS_EC2_METADATA_SERVICE_ENDPOINT", strings.TrimSpace(endpoint))
Defensive patterns

Strategy: try-catch

Validate before calling

if ep := os.Getenv("AWS_EC2_METADATA_SERVICE_ENDPOINT"); ep != "" {
    if _, err := url.Parse(strings.TrimSpace(ep)); err != nil {
        return fmt.Errorf("bad IMDS endpoint: %w", err)
    }
}

Try / catch

var urlErr *url.Error
if errors.As(err, &urlErr) {
    if errors.Is(urlErr.Err, context.Canceled) || errors.Is(urlErr.Err, context.DeadlineExceeded) {
        // extend timeout and retry
    }
}

Prevention

When it happens

Trigger: The IMDS base URL (from AWS_EC2_METADATA_SERVICE_ENDPOINT or the default) produces an invalid request URL after trimming/concatenation, or the passed context is already canceled/expired before the token PUT.

Common situations: Endpoint env var with embedded whitespace or control characters; context deadline exceeded before the call; programmatically constructed base URL missing the scheme.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/edeb58b0df49c73d. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/awscreds/awscreds.go:554

func (p *Provider) fromIMDS(ctx context.Context) (*result, error) {
	if strings.EqualFold(p.env("AWS_EC2_METADATA_DISABLED"), "true") {
		return nil, nil
	}
	base := p.env("AWS_EC2_METADATA_SERVICE_ENDPOINT")
	if base == "" {
		base = defaultIMDSBase
	}
	if err := checkIMDSEndpoint(base); err != nil {
		return nil, err
	}
	base = strings.TrimSuffix(base, "/")

	// IMDSv2 only: a v1 fallback would leave the proxy vulnerable to the SSRF
	// class the session token exists to close.
	tokenReq, err := http.NewRequestWithContext(ctx, http.MethodPut, base+"/latest/api/token", nil)
	if err != nil {
		return nil, fmt.Errorf("awscreds: build imds token request: %w", err)
	}
	// One minute: this token authorizes the two metadata GETs immediately below
	// and is then dropped. The six-hour maximum only widens the window in which a
	// leaked token is still usable.
	tokenReq.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "60")
	tokenBody, err := p.doJSON(p.link, tokenReq, "imds token")
	if err != nil {
		return nil, err
	}
	token := strings.TrimSpace(string(tokenBody))
	if token == "" {
		return nil, errors.New("awscreds: imds returned an empty session token")
	}

	roleBody, err := p.imdsGet(ctx, base+"/latest/meta-data/iam/security-credentials/", token, "imds role")
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 3ee70a1026)