apache/pulsar · error · RestException

Invalid combination of Original principal cannot be empty if

Error message

Invalid combination of Original principal cannot be empty if the request is via proxy.

What it means

When a request arrives through a proxy, the authenticated principal is the proxy role and the real caller is conveyed as the original principal. validateOriginalPrincipal consults AuthorizationService.isValidOriginalPrincipal; if the combination is invalid — e.g. the original principal is empty/blank while the request comes via a proxy role — the admin resource rejects the call with HTTP 401 and this message.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/web/PulsarWebResource.java:201

    public boolean isRequestHttps() {
        return "https".equalsIgnoreCase(httpRequest.getScheme());
    }

    public String getWebServiceListenerName() {
        if (httpRequest == null) {
            return null;
        }
        return (String) httpRequest.getAttribute(WebService.ATTRIBUTE_LISTENER_NAME);
    }

    public static boolean isClientAuthenticated(String appId) {
        return appId != null;
    }

    private void validateOriginalPrincipal(String authenticatedPrincipal, String originalPrincipal) {
        if (!pulsar.getBrokerService().getAuthorizationService()
                .isValidOriginalPrincipal(authenticatedPrincipal, originalPrincipal, clientAuthData())) {
            throw new RestException(Status.UNAUTHORIZED,
                    "Invalid combination of Original principal cannot be empty if the request is via proxy.");
        }
    }

    protected boolean hasSuperUserAccess() {
        try {
            validateSuperUserAccess();
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public CompletableFuture<Void> validateSuperUserAccessAsync() {
        if (!config().isAuthenticationEnabled() || !config().isAuthorizationEnabled()) {
            return CompletableFuture.completedFuture(null);
        }
        String appId = clientAppId();

View on GitHub (pinned to 820761864e)

Solutions

  1. Configure the client/proxy to send the X-Original-Principal header with the real caller role
  2. Verify the proxy is in broker's proxyRoles so the original-principal logic is applied
  3. If not using a proxy, connect directly to the broker so no original principal is required
  4. Check AuthorizationService.isValidOriginalPrincipal logic/implementation for your auth provider

Example fix

// before (proxy-forwarded request)
curl -H "Authorization: Bearer <proxy-token>" http://proxy:8080/admin/v2/clusters
// after
curl -H "Authorization: Bearer <proxy-token>" -H "X-Original-Principal: admin-client" http://proxy:8080/admin/v2/clusters
Defensive patterns

Strategy: validation

Validate before calling

// client-side: ensure original principal is forwarded when going through a proxy
if (usingProxy && (originalPrincipal == null || originalPrincipal.isBlank())) {
    throw new IllegalStateException("X-Original-Principal must be set when connecting through a proxy");
}

Try / catch

try {
    admin.tenants().getTenant(tenant);
} catch (PulsarAdminException e) {
    if (e.getStatusCode() == 401) {
        // check proxy/original-principal configuration
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling an admin REST endpoint through a proxy without setting the X-Original-Principal header (or setting it empty); a client behind proxyRoles whose forwarded original principal is blank; misconfigured proxy that strips the original-principal header.

Common situations: Deployments fronting the broker with Pulsar proxy but clients not configured to pass original principal; custom gateways dropping the header; authentication enabled but proxy forwarding not set up.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/8a1fd56b6999b2a3. Report an issue: GitHub.