{"record":{"id":"c34eb5f27b1d7a4f","repo":"apache/pulsar","slug":"unauthorized-to-validateclusterpolicyoperation-for","errorCode":null,"errorMessage":"Unauthorized to validateClusterPolicyOperation for originalPrincipal [${principal}] and clientAppId [${clientAppId}] about operation [${operation}] on cluster [${cluster}]","messagePattern":"Unauthorized to validateClusterPolicyOperation for originalPrincipal \\[(.+?)\\] and clientAppId \\[(.+?)\\] about operation \\[(.+?)\\] on cluster \\[(.+?)\\]","errorType":"http","errorClass":"RestException","httpStatus":401,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/ClustersBase.java","lineNumber":1327,"sourceCode":"                                                  + \"about operation [%s] on cluster [%s]\",\n                                            originalPrincipal(), clientAppId(), operation.toString(), cluster));\n                        }\n                    });\n        }\n        return CompletableFuture.completedFuture(null);\n    }\n\n    private CompletableFuture<Void> validateClusterPolicyOperationAsync(String cluster, PolicyName policyName,\n                                                                        PolicyOperation operation) {\n        final var pulsar = pulsar();\n        if (pulsar.getBrokerService().isAuthenticationEnabled()\n            && pulsar.getBrokerService().isAuthorizationEnabled()) {\n            return pulsar.getBrokerService().getAuthorizationService()\n                    .allowClusterPolicyOperationAsync(cluster, policyName, operation, originalPrincipal(),\n                            clientAppId(), clientAuthData())\n                    .thenAccept(isAuthorized -> {\n                        if (!isAuthorized) {\n                            throw new RestException(Status.UNAUTHORIZED,\n                                    String.format(\"Unauthorized to validateClusterPolicyOperation for\"\n                                                  + \" originalPrincipal [%s] and clientAppId [%s] \"\n                                                  + \"about operation [%s] on cluster [%s]\",\n                                            originalPrincipal(), clientAppId(), operation.toString(), cluster));\n                        }\n                    });\n        }\n        return CompletableFuture.completedFuture(null);\n    }\n}\n","sourceCodeStart":1309,"sourceCodeEnd":1338,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/ClustersBase.java#L1309-L1338","documentation":"This 401 UNAUTHORIZED error is thrown when the cluster-policy-level authorization check fails. For operations on a named cluster policy (e.g. namespace-level policies applied at cluster scope), the broker calls AuthorizationService.allowClusterPolicyOperationAsync; a false result produces this RestException. It identifies the original principal, client app id, policy operation, and cluster.","triggerScenarios":"Invoking admin endpoints that validate cluster policy operations (validateClusterPolicyOperationAsync path) while authorizationEnabled=true and the role has no grant for the given policyName/operation on the cluster.","commonSituations":"Authorization provider lacks policy-level grant support or the grant for that specific policy name was never created; using a read-only role for a policy mutation (PUT/POST); grant recorded for a different cluster name.","solutions":["Create the policy grant for the role: allow ClusterPolicyOperation for the specific policyName and cluster in your authorization provider.","Elevate the role to superUserRoles if it is meant to manage all policies.","Check the authorization provider logs/implementation to confirm allowClusterPolicyOperationAsync is wired correctly (custom providers may default to false).","Verify the cluster name in the request matches the cluster where the grant exists."],"exampleFix":"// before: role not granted -> 401\n// after: grant policy operation in custom AuthorizationProvider\n@Override\npublic boolean allowClusterPolicyOperation(String cluster, String policyName,\n    PolicyOperation operation, String role, AuthenticationDataSource authData) {\n    return policyGrants.getOrDefault(role, Set.of())\n        .contains(cluster + \":\" + policyName + \":\" + operation);\n}","handlingStrategy":"validation","validationCode":"// verify policy grants exist before invoking policy operations\nif (!policyGrants.containsKey(role)) { /* create grant for policyName/operation */ }","typeGuard":null,"tryCatchPattern":"try { admin.clusters().updateClusterPolicyOperation(...); }\ncatch (PulsarAdminException e) {\n  if (e.getStatusCode() == 401) { /* add the missing policy grant */ }\n  else throw e;\n}","preventionTips":["Grant specific ClusterPolicyOperations per role, not just cluster-level grants","Confirm custom AuthorizationProviders implement allowClusterPolicyOperationAsync rather than defaulting to false","Verify cluster name in the grant matches the request","Test policy operations with a dedicated non-superuser role in staging"],"tags":["authorization","rest-api","admin","policy"],"backgroundTag":"broker-authorization-denied","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}