{"record":{"id":"b00efed01e9b6936","repo":"grpc/grpc-go","slug":"message-authentication-failed","errorCode":null,"errorMessage":"message authentication failed","messagePattern":"message authentication failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/alts/internal/conn/common.go","lineNumber":35,"sourceCode":" */\n\npackage conn\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nconst (\n\t// GcmTagSize is the GCM tag size is the difference in length between\n\t// plaintext and ciphertext. From crypto/cipher/gcm.go in Go crypto\n\t// library.\n\tGcmTagSize = 16\n)\n\n// ErrAuth occurs on authentication failure.\nvar ErrAuth = errors.New(\"message authentication failed\")\n\n// SliceForAppend takes a slice and a requested number of bytes. It returns a\n// slice with the contents of the given slice followed by that many bytes and a\n// second slice that aliases into it and contains only the extra bytes. If the\n// original slice has sufficient capacity then no allocation is performed.\nfunc SliceForAppend(in []byte, n int) (head, tail []byte) {\n\tif total := len(in) + n; cap(in) >= total {\n\t\thead = in[:total]\n\t} else {\n\t\thead = make([]byte, total)\n\t\tcopy(head, in)\n\t}\n\ttail = head[len(in):]\n\treturn head, tail\n}\n\n// ParseFramedMsg parse the provided buffer and returns a frame of the format\n// msgLength+msg and any remaining bytes in that buffer.","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/alts/internal/conn/common.go#L17-L53","documentation":"Thrown by newEngine when the RBAC policy's action field is neither RBAC_ALLOW nor RBAC_DENY. The gRPC RBAC engine only supports ALLOW and DENY actions. RBAC_LOG is handled earlier (at the HTTP filter level in parseConfig, which treats LOG as a no-op), and RBAC_UNSPECIFIED (the zero value / action not set) reaches newEngine only if the filter-level no-op check is bypassed. The error includes config.Action (the proto enum name) for diagnosis.","triggerScenarios":"NewChainEngine is called directly (not through the HTTP filter's parseConfig) with a policy whose action is RBAC_LOG or RBAC_UNSPECIFIED. In the normal xDS flow, parseConfig handles LOG by returning an empty config (no-op) before calling NewChainEngine, so this error typically surfaces only when application code or tests construct a ChainEngine directly with an unsupported action. It can also surface if parseConfig's LOG check has a bug or if the action is UNSPECIFIED (0) which parseConfig does not special-case.","commonSituations":"Application code calling rbac.NewChainEngine directly with a hand-built policy that leaves action unset (defaults to UNSPECIFIED) or explicitly sets LOG. A test that exercises the engine directly. A control plane bug that sends an UNSPECIFIED action past the filter-level no-op guard (the guard checks for LOG specifically, not UNSPECIFIED).","solutions":["Set the RBAC policy's action to RBAC_ALLOW or RBAC_DENY before calling NewChainEngine.","If calling NewChainEngine directly, validate that config.GetAction() is ALLOW or DENY before invoking, and handle LOG/UNSPECIFIED at the caller level (as the HTTP filter does).","If the action is genuinely UNSPECIFIED in the xDS resource, fix the control plane to always set an explicit action."],"exampleFix":"// before: policy with unspecified action\npolicy := &v3rbacpb.RBAC{\n    // action defaults to RBAC_UNSPECIFIED (0)\n    Policies: map[string]*v3rbacpb.Policy{...},\n}\nengine, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{policy}, \"\")\n// err = \"unsupported action UNSPECIFIED\"\n\n// after: explicitly set ALLOW or DENY\npolicy := &v3rbacpb.RBAC{\n    Action:   v3rbacpb.RBAC_ALLOW,\n    Policies: map[string]*v3rbacpb.Policy{...},\n}\nengine, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{policy}, \"\")","handlingStrategy":"validation","validationCode":"// Validate the action before calling NewChainEngine:\nfunc validateRBACAction(rbac *v3rbacpb.RBAC) error {\n    switch rbac.GetAction() {\n    case v3rbacpb.RBAC_ALLOW, v3rbacpb.RBAC_DENY:\n        return nil\n    case v3rbacpb.RBAC_LOG:\n        return fmt.Errorf(\"LOG action should be handled as no-op before reaching the engine\")\n    default:\n        return fmt.Errorf(\"unsupported RBAC action: %s (must be ALLOW or DENY)\", rbac.GetAction())\n    }\n}\n\n// Call before:\nif err := validateRBACAction(policy); err != nil { return err }\nengine, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{policy}, \"\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set an explicit action (ALLOW or DENY) on RBAC policies; never rely on the zero value.","If calling NewChainEngine directly, pre-validate the action and handle LOG/UNSPECIFIED at the caller level.","Ensure the control plane always emits an explicit action field in RBAC rules."],"tags":["xds","rbac","grpc","action","validation"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}