{"record":{"id":"55c1d301a14d8f46","repo":"kubernetes/kops","slug":"url-not-valid-for-sts-request","errorCode":null,"errorMessage":"url not valid for STS request","messagePattern":"url not valid for STS request","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/bootstrap/awsbootstrap/verifier.go","lineNumber":403,"sourceCode":"\tif u.Query().Get(\"Action\") != \"GetCallerIdentity\" {\n\t\treturn false\n\t}\n\tif len(u.Query()[\"Action\"]) != 1 {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// getCallerIdentityV2 will request the presigned token URL, and decode the returned identity.\nfunc (s *stsRequestValidator) getCallerIdentityV2(ctx context.Context, httpClient *http.Client, decoded *awsV2Token) (*GetCallerIdentityResponse, error) {\n\treqURL, err := url.Parse(decoded.URL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing STS request URL: %w\", err)\n\t}\n\n\tif !s.isValidV2(reqURL) {\n\t\treturn nil, fmt.Errorf(\"url not valid for STS request\")\n\t}\n\n\treq := &http.Request{\n\t\tURL:    reqURL,\n\t\tMethod: decoded.Method,\n\t\tHeader: decoded.SignedHeader,\n\t}\n\tresponse, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"sending STS request: %v\", err)\n\t}\n\tif response != nil {\n\t\tdefer response.Body.Close()\n\t}\n\n\tresponseBody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading STS response: %v\", err)","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/bootstrap/awsbootstrap/verifier.go#L385-L421","documentation":"The presigned STS URL comes from an untrusted token, so isValidV2 enforces a strict shape before the verifier will contact it: https scheme, host equal to the configured STS host, path \"/\", and exactly one Action=GetCallerIdentity query parameter. Any deviation — including an http:// URL that would leak the request in plaintext — triggers this error, preventing SSRF and downgrade attacks.","triggerScenarios":"getCallerIdentityV2 (via verifyTokenV2) parses the token's URL successfully but isValidV2 returns false: scheme != https, Host != s.Host, Path != \"/\", Action query param != GetCallerIdentity, or Action present more than once.","commonSituations":"Tokens presigned against a different regional STS endpoint than the verifier expects (s.Host mismatch); client built the URL with http instead of https; duplicated Action parameter after URL re-encoding; man-in-the-middle or malicious token; presigning with a tool that appends extra query params.","solutions":["Regenerate the presigned URL with the AWS SDK against the STS endpoint matching the verifier's configured stsRequestValidator.Host (same region), using https.","Ensure the presign uses the standard SDK Presign (GetCallerIdentity, GET) rather than hand-rolled URL construction.","Compare the URL host on the failing token with s.Host to confirm region/endpoint mismatch, then align client region config.","If tokens are proxied, verify no component rewrites scheme, host, path, or duplicates the Action query parameter."],"exampleFix":"// Client-side presign must target the verifier's STS host over https:\n// before (hand-built, wrong region/scheme)\nurl := \"http://sts.us-east-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&...\"\n// after\nreq, _ := stsclient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})\npresigned, _ := stsclient.PresignGetCallerIdentity(ctx, req,\n  sts.WithPresignClient(presignClient)) // https, correct region, Action=GetCallerIdentity","handlingStrategy":"try-catch","validationCode":"u, err := url.Parse(token.URL)\nif err != nil { return err }\nvalid := u.Scheme == \"https\" &&\n  u.Host == expectedSTSHost &&\n  u.Path == \"/\" &&\n  u.Query().Get(\"Action\") == \"GetCallerIdentity\" &&\n  len(u.Query()[\"Action\"]) == 1\nif !valid { return fmt.Errorf(\"presigned URL host/scheme/action mismatch: host=%s scheme=%s\", u.Host, u.Scheme) }","typeGuard":"func isWellFormedPresignedSTSURL(raw, expectedHost string) bool {\n  u, err := url.Parse(raw)\n  if err != nil { return false }\n  return u.Scheme == \"https\" && u.Host == expectedHost && u.Path == \"/\" &&\n    u.Query().Get(\"Action\") == \"GetCallerIdentity\" && len(u.Query()[\"Action\"]) == 1\n}","tryCatchPattern":"result, err := verifier.VerifyToken(ctx, token)\nif err != nil && strings.Contains(err.Error(), \"url not valid for STS request\") {\n  // client presigned against wrong region/endpoint or http; re-presign on the node\n  return nil, fmt.Errorf(\"check node's AWS region config and STS endpoint; regenerate token: %w\", err)\n}","preventionTips":["Presign GetCallerIdentity with the official AWS SDK against the STS regional endpoint the verifier expects (matching s.Host).","Never rewrite or proxy the presigned URL — re-encoding can duplicate query params or change scheme.","Always use https; http URLs are rejected by design to prevent plaintext token leakage.","Keep node region configuration consistent with the kOps cluster region."],"tags":["aws","sts","security","url-validation"],"backgroundTag":"sts-presigned-url-validation-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}