{"record":{"id":"4c50971484ee2ce7","repo":"apache/pulsar","slug":"unauthorized-to-validatebrokeroperation-for-origin","errorCode":null,"errorMessage":"Unauthorized to validateBrokerOperation for originalPrincipal [${principal}] and clientAppId [${clientAppId}] about operation [${operation}] on broker [${brokerId}]","messagePattern":"Unauthorized to validateBrokerOperation for originalPrincipal \\[(.+?)\\] and clientAppId \\[(.+?)\\] about operation \\[(.+?)\\] on broker \\[(.+?)\\]","errorType":"http","errorClass":"RestException","httpStatus":401,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/BrokersBase.java","lineNumber":589,"sourceCode":"                    throw new RestException(Status.UNAUTHORIZED,\n                            String.format(\"Unauthorized to validateBothSuperuserAndBrokerOperation for\"\n                                          + \" originalPrincipal [%s] and clientAppId [%s] \"\n                                          + \"about operation [%s] on broker [%s]\",\n                                    originalPrincipal(), clientAppId(), operation.toString(), brokerId));\n                });\n    }\n\n    private CompletableFuture<Void> validateBrokerOperationAsync(String cluster, String brokerId,\n                                                                 BrokerOperation operation) {\n        final var pulsar = pulsar();\n        if (pulsar.getBrokerService().isAuthenticationEnabled()\n            && pulsar.getBrokerService().isAuthorizationEnabled()) {\n            return pulsar.getBrokerService().getAuthorizationService()\n                    .allowBrokerOperationAsync(cluster, brokerId, operation, originalPrincipal(),\n                            clientAppId(), clientAuthData())\n                    .thenAccept(isAuthorized -> {\n                        if (!isAuthorized) {\n                            throw new RestException(Status.UNAUTHORIZED,\n                                    String.format(\"Unauthorized to validateBrokerOperation for\"\n                                                  + \" originalPrincipal [%s] and clientAppId [%s] \"\n                                                  + \"about operation [%s] on broker [%s]\",\n                                            originalPrincipal(), clientAppId(), operation.toString(), brokerId));\n                        }\n                    });\n        }\n        return CompletableFuture.completedFuture(null);\n    }\n}\n","sourceCodeStart":571,"sourceCodeEnd":600,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/BrokersBase.java#L571-L600","documentation":"validateBrokerOperationAsync consults the AuthorizationService (allowBrokerOperationAsync) when authentication is enabled and authorization is enabled; if the provider denies the requested broker operation for the original principal and clientAppId, it throws HTTP 401 UNAUTHORIZED with this message. Unlike error 67, this path only checks broker-operation authorization (no superuser bypass).","triggerScenarios":"Called via brokerOperationValidation from broker admin endpoints when: authentication enabled, authorization enabled, and the authorization provider returns false for allowBrokerOperationAsync(cluster, brokerId, operation, originalPrincipal, clientAppId, clientAuthData).","commonSituations":"Custom AuthorizationProvider rules not covering the BrokerOperation being requested; role absent from the external authorization store; wildcard/regex role matching disabled so 'admin-*' patterns don't apply; client auth data (peer cert / token) missing role claims after a TLS or token migration.","solutions":["Grant the principal's role the required broker-operation permission in your AuthorizationProvider / external store, matching the exact role name shown as originalPrincipal in the message.","Add the role to superUserRoles if it should bypass authorization entirely (superusers skip this check at the validateBothSuperuserAndBrokerOperation layer).","Review and fix custom AuthorizationProvider logic (or switch to the default PulsarAuthorizationProvider) so allowBrokerOperationAsync returns true for legitimate admin roles.","Verify the client is sending valid credentials conveying the intended role (expired/rotated token is a frequent cause)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// ensure the role is authorized for broker operations before calling admin APIs\nboolean allowed = authorizationProvider\n        .allowBrokerOperationAsync(cluster, brokerId, operation, role, appId, authData).join();\nif (!allowed) { throw new SecurityException(\"role not authorized: \" + role); }","typeGuard":null,"tryCatchPattern":"try {\n    brokerOperationValidation(...).get();\n} catch (ExecutionException e) {\n    if (e.getCause() instanceof PulsarAdminException.NotAuthorizedException) {\n        handleDenied(operation, brokerId);\n    }\n}","preventionTips":["Define explicit broker-operation grants for every admin role in your authorization provider.","Avoid relying on wildcard role matching unless authorizationAllowWildcardsMatching is enabled.","Log and review originalPrincipal/clientAppId mismatches — they reveal credential drift.","When a role should be unrestricted, add it to superUserRoles rather than custom authz rules."],"tags":["pulsar","authorization","http-401","broker-operation"],"backgroundTag":"unauthorized-admin-operation","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}