traefik-forward-auth icon indicating copy to clipboard operation
traefik-forward-auth copied to clipboard

Keycloak Documentation

Open thomseddon opened this issue 4 years ago • 4 comments

Includes notes from #134 and instructions on how to enable email passthrough with X-Forwarded-User header

thomseddon avatar Aug 07 '20 20:08 thomseddon

Currently setting this label on my traefik-forward-auth container: traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders: "X-Forwarded-User"

Results in the correct users email being specified with a default keycloak OIDC setup: "X-Forwarded-User":["[email protected]"]

Is there any other settings you need?

michael-robbins avatar Aug 08 '20 00:08 michael-robbins

Here's a Keycloak script I rigged up that performs RBAC based on Client Roles in keycloak and integrates nicely with Traefik Forward Auth and OIDC.

It first verifies the user is in an overarching 'proxy_access' client role, then an application specific client role.

// Imports
FormMessage = Java.type('org.keycloak.models.utils.FormMessage');
KeycloakUriBuilder = Java.type('org.keycloak.common.util.KeycloakUriBuilder');

Matcher = Java.type('java.util.regex.Matcher');
Pattern = Java.type('java.util.regex.Pattern');


function authenticate(context) {
    // Obtain the generic client
    var client = session.getContext().getClient();

    // Verify Proxy Access
    var proxyName = "proxy_access";
    var proxyClientRole = client.getRole(proxyName);
    
    if (proxyClientRole === null) {
        context.forkWithErrorMessage(new FormMessage('label', 'Proxy Access Role \'' + proxyName + '\' is missing?'));
        return;
    }

    if (! user.hasRole(proxyClientRole)) {
        context.forkWithErrorMessage(new FormMessage('label', 'User is not in the \'' + proxyClientRole.getDescription() + '\' role.'));
        return;
    }

    // Verify Application Access
    var referer = httpRequest.getHttpHeaders().getHeaderString("Referer");

    var refererUri = KeycloakUriBuilder.fromUri(referer).build();
    var query = refererUri.getQuery();

    // We extract the 'source' application from the state query parameter
    var appPattern = Pattern.compile("state=[0-9a-f]+:oidc:https://(.*)/");
    var matcher = appPattern.matcher(query);

    if (matcher.find()) {
        var appName = matcher.group(1);
        var appClientRole = client.getRole(appName);
        
        if (appClientRole === null) {
            context.forkWithErrorMessage(new FormMessage('label', 'App Access Role \'' + appName + '\' is missing?'));
            return;
        }

        if (! user.hasRole(appClientRole)) {
            context.forkWithErrorMessage(new FormMessage('label', 'User is not in the \'' + appClientRole.getDescription() + '\' role.'));
            return;
        }
    } else {
        context.forkWithErrorMessage(new FormMessage('label', "Couldn't find the App hostname in the referer?"));
        return;
    }

    context.success();
}

michael-robbins avatar Aug 09 '20 02:08 michael-robbins

Just wanted to say with the above script, this works great for a single application, but if you're protecting multiple services behind a single OIDC app, then this approach won't work, as your cookie for service X will let you go into service Y, as Keycloak doesn't do any auth with the cookies, only the login page.

michael-robbins avatar Oct 18 '20 08:10 michael-robbins

I think instead we'd have to extract the group(s) out of the OIDC token(s) within traefik-forward-auth and then make decisions then based on the users (eg. groups)

michael-robbins avatar Oct 18 '20 08:10 michael-robbins