JuliusBrussee/caveman · error
awscreds: credential expiry
Error message
awscreds: %s credential expiry: %w
What it means
credentialsFromJSON passes the parsed Expiration field to parseExpiry; if that cannot produce a valid time, the credential document's expiry is malformed and this wrapped error is returned. The library throws it because credentials without a parsable expiration cannot be safely cached or refreshed.
Solutions
- Inspect the Expiration field in the endpoint's raw response; it must be an RFC3339 timestamp like 2026-01-02T15:04:05Z.
- If using a custom/mock metadata endpoint, fix it to emit a valid RFC3339 Expiration.
- If the body looks corrupted, check for proxy interference and retry against the real metadata service.
- Update the library if the service changed its timestamp format (rare AWS-side format changes).
Example fix
// before (mock IMDS response)
{"AccessKeyID":"AKIA...","SecretAccessKey":"...","Token":"...","Expiration":"tomorrow"}
// after
{"AccessKeyID":"AKIA...","SecretAccessKey":"...","Token":"...","Expiration":"2026-09-21T00:00:00Z"} Defensive patterns
Strategy: validation
Validate before calling
func hasRFC3339Expiration(body []byte) bool {
var p struct{ Expiration string `json:"Expiration"` }
if json.Unmarshal(body, &p) != nil { return false }
_, err := time.Parse(time.RFC3339, p.Expiration)
return err == nil
} Try / catch
creds, err := provider.Credentials(ctx)
var expiryErr *time.ParseError
if errors.As(err, &expiryErr) {
return fmt.Errorf("metadata endpoint sent bad Expiration (%q): check mock/proxy", expiryErr.Value)
} Prevention
- Ensure mock/test metadata servers emit RFC3339 timestamps.
- Never hand-edit metadata responses; regenerate them from the real service.
- Cover credential parsing in unit tests with a golden RFC3339 expiration.
When it happens
Trigger: fromContainer or fromIMDS receives credential JSON whose Expiration is missing, empty, not RFC3339/expected ISO8601 format, or zero-valued.
Common situations: A fake/placeholder metadata endpoint in local development returning incomplete JSON; a hand-rolled mock ECS agent with a wrong date format; an intercepted or corrupted response body; unusual custom AWS_EC2_METADATA_SERVICE_ENDPOINT implementations.
Related errors
- awscreds: : http
- awscreds: returned an unparseable response
- awscreds: returned code
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- awscreds: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/bcc129c8b6ae0e9e.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/awscreds/awscreds.go:628
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{
AccessKeyID: strings.TrimSpace(parsed.AccessKeyID),
SecretAccessKey: strings.TrimSpace(parsed.SecretAccessKey),
SessionToken: strings.TrimSpace(parsed.Token),
},
expires: expires,
source: source,
}, nil
}
// parseExpiry maps an absent expiry to the zero time, which means "never
// refresh" to the cache.
func parseExpiry(raw string) (time.Time, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return time.Time{}, nilView on GitHub (pinned to 3ee70a1026)