{"record":{"id":"66fbb2d68abb141c","repo":"kubernetes/kops","slug":"incorrect-audience","errorCode":null,"errorMessage":"incorrect Audience","messagePattern":"incorrect Audience","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/bootstrap/pkibootstrap/pkiverifier/verifier.go","lineNumber":89,"sourceCode":"\n\ttokenBytes, err := base64.StdEncoding.DecodeString(authToken)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"decoding authorization token: %w\", err)\n\t}\n\n\ttoken := &pkibootstrap.AuthToken{}\n\tif err = json.Unmarshal(tokenBytes, token); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"unmarshalling authorization token: %w\", err)\n\t}\n\n\ttokenData := &pkibootstrap.AuthTokenData{}\n\tif err := json.Unmarshal(token.Data, tokenData); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"unmarshalling authorization token data: %w\", err)\n\t}\n\n\t// Guard against replay attacks\n\tif tokenData.Audience != pkibootstrap.AudienceNodeAuthentication {\n\t\treturn nil, nil, fmt.Errorf(\"incorrect Audience\")\n\t}\n\ttimeSkew := math.Abs(time.Since(time.Unix(tokenData.Timestamp, 0)).Seconds())\n\tif timeSkew > float64(v.opt.MaxTimeSkew) {\n\t\treturn nil, nil, fmt.Errorf(\"incorrect Timestamp %v\", tokenData.Timestamp)\n\t}\n\n\t// Verify the token has signed the body content.\n\trequestHash := sha256.Sum256(body)\n\tif !bytes.Equal(requestHash[:], tokenData.RequestHash) {\n\t\treturn nil, nil, fmt.Errorf(\"incorrect RequestHash\")\n\t}\n\n\treturn token, tokenData, nil\n}\n\n// Can generate keys with\n// openssl ecparam -name prime256v1 -genkey -noout -out ec-priv-key.pem\n// openssl ec -in ec-priv-key.pem -pubout > ec-pub-key.pem","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/bootstrap/pkibootstrap/pkiverifier/verifier.go#L71-L107","documentation":"The verifier rejects the token because its Audience claim does not equal pkibootstrap.AudienceNodeAuthentication (\"kops.k8s.io/node-bootstrap\"). This is a replay/cross-use guard: tokens minted for one purpose (or another audience) must not be replayed to the node-bootstrap endpoint. The token itself decoded and its signature was NOT yet checked at this point — this check runs before signature verification.","triggerScenarios":"parseTokenData (verifier.go:88) raises this whenever tokenData.Audience != \"kops.k8s.io/node-bootstrap\": the claim is empty (older client that omits audience), the client sets a different audience string, the token was minted for a different kOps endpoint/audience and replayed here, or a custom authenticator builds AuthTokenData without setting Audience.","commonSituations":"Version skew between nodeup and kops-controller (audience field added/renamed between releases); a homegrown bootstrap client that forgot the Audience field; copying tokens between environments or endpoints; tampering attempts caught by the guard as designed.","solutions":["Regenerate the token on the node using the current pkiAuthenticator.CreateToken, which always sets Audience: AudienceNodeAuthentication.","Align nodeup and kops-controller to the same kOps version so both sides agree on the audience constant.","If using a custom authenticator, set Audience: pkibootstrap.AudienceNodeAuthentication in the AuthTokenData before signing.","Inspect the failing token's claims (base64-decode, then json.Unmarshal the Data field) to confirm the audience value actually sent."],"exampleFix":"// before\ndata := AuthTokenData{Timestamp: time.Now().Unix(), RequestHash: requestHash[:], KeyID: keyID, Instance: hostname}\n// after\ndata := AuthTokenData{Timestamp: time.Now().Unix(), Audience: pkibootstrap.AudienceNodeAuthentication, RequestHash: requestHash[:], KeyID: keyID, Instance: hostname}","handlingStrategy":"validation","validationCode":"// Verify the audience claim before sending the bootstrap request\nfunc audienceIsNodeBootstrap(authHeader, prefix string) bool {\n\traw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, prefix))\n\tif err != nil {\n\t\treturn false\n\t}\n\tvar t pkibootstrap.AuthToken\n\tif json.Unmarshal(raw, &t) != nil {\n\t\treturn false\n\t}\n\tvar d pkibootstrap.AuthTokenData\n\tif json.Unmarshal(t.Data, &d) != nil {\n\t\treturn false\n\t}\n\treturn d.Audience == pkibootstrap.AudienceNodeAuthentication\n}","typeGuard":"func hasNodeBootstrapAudience(d *pkibootstrap.AuthTokenData) bool {\n\treturn d != nil && d.Audience == pkibootstrap.AudienceNodeAuthentication\n}","tryCatchPattern":"result, err := verifier.VerifyToken(ctx, req, authToken, body)\nif err != nil {\n\tif strings.Contains(err.Error(), \"incorrect Audience\") {\n\t\t// do not retry with the same token; it was minted for the wrong audience\n\t\tklog.Errorf(\"bootstrap token audience rejected: %v\", err)\n\t\treturn nil, fmt.Errorf(\"token audience mismatch: %w\", err)\n\t}\n\treturn nil, err\n}","preventionTips":["Reference pkibootstrap.AudienceNodeAuthentication as a constant in custom minting code — never hardcode the string.","Keep nodeup and kops-controller versions aligned; the audience contract can change between releases.","Never reuse tokens minted for other kOps endpoints or audiences.","Unit-test any custom authenticator against the real verifier before deploying."],"tags":["go","authentication","pki","replay-attack","bootstrap"],"backgroundTag":"invalid-audience-claim","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"}