JuliusBrussee/caveman · error

awscreds: : http

Error message

awscreds: %s: http %d

What it means

doJSON performs one HTTP attempt against an AWS credential endpoint (ECS container metadata or IMDS) and returns the response body only when the status is 2xx. When the endpoint answers with any non-2xx status (404, 403, 500, etc.), it aborts with this error, deliberately reporting only the numeric status because a metadata response body can contain credential material. It is a generic HTTP status failure from the AWS metadata service.

Solutions

  1. Identify which 'what' (imds token, imds role, imds credentials, container metadata) failed from the message and check the corresponding endpoint: verify AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or the IMDS base (AWS_EC2_METADATA_SERVICE_ENDPOINT).
  2. For 'imds credentials: http 404', attach an IAM instance profile to the EC2 instance (aws ec2 associate-iam-instance-profile) or pick a role listed at /latest/meta-data/iam/security-credentials/.
  3. For http 403/404 on the token request, confirm IMDSv2 is enabled in the instance metadata options and no firewall blocks 169.254.169.254.
  4. For 5xx, retry after a short delay; metadata services throttle briefly.
  5. If running in a non-AWS environment (local dev, other clouds), stop relying on metadata credentials and supply explicit credentials.

Example fix

// before
creds, err := provider.Credentials(ctx) // fails: awscreds: imds credentials: http 404
// after
// ensure the instance has a role:
//   aws ec2 associate-iam-instance-profile --instance-id i-123 --iam-instance-profile Name=my-profile
creds, err := provider.Credentials(ctx)
Defensive patterns

Strategy: retry

Validate before calling

// best-effort precheck
if u := os.Getenv("AWS_EC2_METADATA_SERVICE_ENDPOINT"); u == "" && os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") == "" {
    // may still be EC2 IMDS; nothing to precheck client-side
}

Try / catch

creds, err := provider.Credentials(ctx)
if err != nil {
    if strings.Contains(err.Error(), "http 4") {
        // misconfiguration (no role / bad path): do not retry, fix IAM or env
    } else if strings.Contains(err.Error(), "http 5") {
        // transient: retry with backoff
    }
    return fmt.Errorf("aws credential lookup: %w", err)
}

Prevention

When it happens

Trigger: Calling fromContainer when AWS_CONTAINER_CREDENTIALS_RELATIVE_URI points at a wrong/nonexistent path (404); calling fromIMDS or imdsGet when the EC2 instance has no attached IAM role (404 on the security-credentials path), the IMDSv2 token request is rejected (403), or the metadata service returns 5xx; also raised for the imds token PUT when IMDS is throttled or disabled.

Common situations: Running outside ECS/E2 where the metadata env vars are set incorrectly; an EC2 instance whose IAM instance profile was detached after boot; IMDS hop limit or token endpoint misconfigured; IMDS disabled via instance metadata options (returns 403/404); proxy or security software interfering with 169.254.169.254.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

	}
	req.Header.Set("X-aws-ec2-metadata-token", token)
	return p.doJSON(p.link, req, what)
}

// doJSON performs one attempt and returns the bounded body. A non-2xx response
// is reported by status only: a metadata body holds credential material.
func (p *Provider) doJSON(client *http.Client, req *http.Request, what string) ([]byte, error) {
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("awscreds: %s request failed: %w", what, err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody))
	if err != nil {
		return nil, fmt.Errorf("awscreds: read %s response: %w", what, err)
	}
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return nil, fmt.Errorf("awscreds: %s: http %d", what, resp.StatusCode)
	}
	return body, nil
}

func credentialsFromJSON(body []byte, source string) (*result, error) {
	var parsed credentialJSON
	if err := json.Unmarshal(body, &parsed); err != nil {
		return nil, fmt.Errorf("awscreds: %s returned an unparseable response", source)
	}
	if parsed.Code != "" && !strings.EqualFold(parsed.Code, "Success") {
		return nil, fmt.Errorf("awscreds: %s returned code %q", source, parsed.Code)
	}
	expires, err := parseExpiry(parsed.Expiration)
	if err != nil {
		return nil, fmt.Errorf("awscreds: %s credential expiry: %w", source, err)
	}
	return &result{
		creds: awssig.Credentials{

View on GitHub (pinned to 3ee70a1026)