apache/pulsar · warning · RestException
Unauthorized to validateBothTenantOperationAndSuperUser for…
Error message
Unauthorized to validateBothTenantOperationAndSuperUser for originalPrincipal [${originalPrincipal}] and clientAppId [${clientAppId}] about operation [${operation}] What it means
HTTP 401 UNAUTHORIZED raised by validateBothSuperUserAndTenantOperation when the caller is neither a superuser (originalPrincipal fails superuser validation) nor authorized for the requested tenant operation (tenant admin role check fails). Both validation paths failed, so the tenancy API call is denied.
Solutions
- Grant the role: add it to superUserRoles in broker.conf (restart or dynamic config) for cluster-wide admin, or
- Add the role to the tenant's adminRoles via PUT /admin/v2/tenants/{tenant} if it should only manage this tenant.
- Verify authentication is mapping the expected role: check clientAppId/originalPrincipal in the message against the credentials presented.
- If behind a proxy, ensure the proxy forwards the original principal (authenticateOriginalAuthData=true) so authorization evaluates the right user.
- Confirm the operation name in the message matches what the client intends; a wrong endpoint may require higher privileges.
Example fix
// broker.conf before
superUserRoles=admin
// after: grant the client role
superUserRoles=admin,my-admin-client
// or, tenant-scoped:
// after
TenantInfo info = admin.tenants().getTenant("my-tenant");
info.getAdminRoles().add("my-tenant-admin");
admin.tenants().updateTenant("my-tenant", info); Defensive patterns
Strategy: try-catch
Validate before calling
// Caller-side pre-check
boolean isSuperUser = brokerConfig.getSuperUserRoles().contains(myRole);
boolean isTenantAdmin = tenantInfo.getAdminRoles().contains(myRole);
if (!isSuperUser && !isTenantAdmin) {
throw new SecurityException("Role " + myRole + " cannot perform tenant operation");
} Try / catch
try {
admin.tenants().getTenants();
} catch (PulsarAdminException e) {
if (e.getStatusCode() == 401) {
// insufficient privileges: request superuser role or tenant adminRoles grant
throw new SecurityException("Not authorized for tenant operation; need superuser or adminRoles", e);
}
throw e;
} Prevention
- Keep superUserRoles and tenant adminRoles in sync with your team's access grants.
- When using a proxy, enable authenticateOriginalAuthData so the real principal is authorized.
- Use a dedicated admin role for tenancy APIs and verify it's present in broker config.
- Test authorization with the exact credentials used in production automation.
When it happens
Trigger: GET/PUT/DELETE /admin/v2/tenants* endpoints (getTenants, getTenantAdmin, createTenant, updateTenant, deleteTenant) invoked by a principal lacking superuser role and not listed in the tenant's adminRoles; common with authenticated clients whose role is not mapped in superUserRoles or the tenant policy.
Common situations: Client authenticated with a role certificate/token but superUserRoles in broker.conf doesn't include it; tenant adminRoles not updated after team changes; proxy/stripped original principal (originalPrincipal not propagated through proxy authentication); use of admin API key without tenant-level grants.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Client is not authorized to perform operation
- Client is not authorized to perform operation
- Client is not authorized to perform operation
- Cluster [ ] is not in the list of allowed clusters list for…
- Clusters do not exist
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/860f565327789ee5.
Report an issue: GitHub.
Appendix: source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/TenantsBase.java:370
}
Throwable superUserValidationException = null;
try {
superUserValidationFuture.join();
} catch (Throwable ex) {
superUserValidationException = FutureUtil.unwrapCompletionException(ex);
}
Throwable tenantOperationValidationException = null;
try {
tenantOperationValidationFuture.join();
} catch (Throwable ex) {
tenantOperationValidationException = FutureUtil.unwrapCompletionException(ex);
}
log.debug().attr("originalPrincipal", originalPrincipal())
.attr("operation", operation.toString())
.attr("superuserValidationError", superUserValidationException)
.attr("tenantOperationValidationError", tenantOperationValidationException)
.log("validateBothTenantOperationAndSuperUser failed");
throw new RestException(Status.UNAUTHORIZED,
String.format("Unauthorized to validateBothTenantOperationAndSuperUser for"
+ " originalPrincipal [%s] and clientAppId [%s] "
+ "about operation [%s] ",
originalPrincipal(), clientAppId(), operation.toString()));
});
}
}
View on GitHub (pinned to 820761864e)