KEYCLOAK-1066 Kerberos credential delegation support
This commit is contained in:
1 parent
8cf31bd3f1
commit
db07d79009
42 files changed
+1189
-89
No files matched your search
@@ -0,0 +1,66 @@
|
||||
Keycloak Example - Kerberos Credential Delegation
|
||||
=================================================
|
||||
|
||||
This example requires that Keycloak is configured with Kerberos/SPNEGO authentication. It's showing how the forwardable TGT is sent from
|
||||
the Keycloak auth-server to the application, which deserializes it and authenticates with it to further Kerberized service, which in the example is LDAP server.
|
||||
|
||||
Example is using built-in ApacheDS Kerberos server from the keycloak testsuite and the realm with preconfigured federation provider and `gss delegation credential` protocol mapper.
|
||||
It also needs to enable forwardable ticket support in Kerberos configuration and your browser.
|
||||
|
||||
Detailed steps:
|
||||
|
||||
**1)** Build and deploy this sample's WAR file. For this example, deploy on the same server that is running the Keycloak Server, although this is not required for real world scenarios.
|
||||
|
||||
|
||||
**2)** Copy `http.keytab` file from the root directory of example to `/tmp` directory (On Linux):
|
||||
|
||||
```
|
||||
cp http.keytab /tmp/http.keytab
|
||||
```
|
||||
|
||||
Alternative is to configure different location for `keyTab` property in `kerberosrealm.json` configuration file (On Windows this will be needed).
|
||||
Note that in production, keytab file should be in secured location accessible just to the user under which is Keycloak server running.
|
||||
|
||||
|
||||
**3)** Run Keycloak server and import `kerberosrealm.json` into it through admin console. This will import realm with sample application
|
||||
and configured LDAP federation provider with Kerberos/SPNEGO authentication support enabled and with `gss delegation credential` protocol mapper
|
||||
added to the application.
|
||||
|
||||
**WARNING:** It's recommended to use JDK8 to run Keycloak server. For JDK7 you may be faced with the bug described [here](http://darranl.blogspot.cz/2014/09/kerberos-encrypteddata-null-key-keytype.html) .
|
||||
Alternatively you can use OpenJDK7 but in this case you will need to use aes256-cts-hmac-sha1-96 for both KDC and Kerberos client configuration. For server,
|
||||
you can add system property to the maven command when running ApacheDS Kerberos server `-Dkerberos.encTypes=aes256-cts-hmac-sha1-96` (see below) and for
|
||||
client add encryption types to configuration file like `/etc/krb5.conf` (but they should be already available. See below).
|
||||
|
||||
|
||||
**4)** Run ApacheDS based Kerberos server embedded in Keycloak. Easiest is to checkout keycloak sources, build and then run KerberosEmbeddedServer
|
||||
as shown here:
|
||||
|
||||
```
|
||||
git clone https://github.com/keycloak/keycloak.git
|
||||
mvn clean install -DskipTests=true
|
||||
cd testsuite/integration
|
||||
mvn exec:java -Pkerberos
|
||||
```
|
||||
|
||||
More details about embedded Kerberos server in [testsuite README](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/README.md#kerberos-server).
|
||||
|
||||
|
||||
**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm and enable `forwardable` flag, which is needed
|
||||
for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server.
|
||||
See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/main/resources/kerberos/test-krb5.conf) for inspiration.
|
||||
|
||||
|
||||
**6)** Configure browser (Firefox, Chrome or other) and enable SPNEGO authentication and credential delegation for `localhost` .
|
||||
In Firefox it can be done by adding `localhost` to both `network.negotiate-auth.trusted-uris` and `network.negotiate-auth.delegation-uris` .
|
||||
More info in [testsuite README](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/README.md#kerberos-server).
|
||||
|
||||
|
||||
**7)** Test the example. Obtain kerberos ticket by running command from CMD (on linux):
|
||||
```
|
||||
kinit hnelson@KEYCLOAK.ORG
|
||||
```
|
||||
with password `secret` .
|
||||
|
||||
Then in your web browser open `http://localhost:8080/kerberos-portal` . You should be logged-in automatically through SPNEGO without displaying Keycloak login screen.
|
||||
Keycloak will also transmit the delegated GSS credential to the application inside access token and application will be able to login with this credential
|
||||
to the LDAP server and retrieve some data from it (Actually it just retrieve few simple data about authenticated user himself).
|
||||
Binary file not shown.
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"id": "kerberos-demo",
|
||||
"realm": "kerberos-demo",
|
||||
"enabled": true,
|
||||
"sslRequired": "external",
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [ "password", "kerberos" ],
|
||||
"defaultRoles": [ "user" ],
|
||||
"scopeMappings": [
|
||||
{
|
||||
"client": "kerberos-app",
|
||||
"roles": [ "user" ]
|
||||
}
|
||||
],
|
||||
"applications": [
|
||||
{
|
||||
"name": "kerberos-app",
|
||||
"enabled": true,
|
||||
"baseUrl": "/kerberos-portal",
|
||||
"redirectUris": [
|
||||
"/kerberos-portal/*"
|
||||
],
|
||||
"adminUrl": "/kerberos-portal",
|
||||
"secret": "password",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"protocolMapper" : "oidc-usermodel-property-mapper",
|
||||
"protocol" : "openid-connect",
|
||||
"name" : "username",
|
||||
"consentText" : "username",
|
||||
"consentRequired" : true,
|
||||
"config" : {
|
||||
"Claim JSON Type" : "String",
|
||||
"user.attribute" : "username",
|
||||
"Token Claim Name" : "preferred_username",
|
||||
"id.token.claim" : "true",
|
||||
"access.token.claim" : "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"protocolMapper" : "oidc-usersessionmodel-note-mapper",
|
||||
"protocol" : "openid-connect",
|
||||
"name" : "gss delegation credential",
|
||||
"consentText" : "gss delegation credential",
|
||||
"consentRequired" : true,
|
||||
"config" : {
|
||||
"user.session.note" : "gss_delegation_credential",
|
||||
"Token Claim Name" : "gss_delegation_credential",
|
||||
"id.token.claim" : "false",
|
||||
"access.token.claim" : "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name": "user",
|
||||
"description": "Have User privileges"
|
||||
}
|
||||
]
|
||||
},
|
||||
"userFederationProviders": [
|
||||
{
|
||||
"displayName": "kerberos-ldap-provider",
|
||||
"providerName": "ldap",
|
||||
"priority": 1,
|
||||
"fullSyncPeriod": -1,
|
||||
"changedSyncPeriod": -1,
|
||||
"config": {
|
||||
"syncRegistrations" : "false",
|
||||
"userAccountControlsAfterPasswordUpdate" : "true",
|
||||
"connectionPooling" : "true",
|
||||
"pagination" : "true",
|
||||
"allowKerberosAuthentication" : "true",
|
||||
"debug" : "true",
|
||||
"editMode" : "WRITABLE",
|
||||
"vendor" : "other",
|
||||
"usernameLDAPAttribute" : "uid",
|
||||
"userObjectClasses" : "inetOrgPerson, organizationalPerson",
|
||||
"connectionUrl" : "ldap://localhost:10389",
|
||||
"baseDn" : "dc=keycloak,dc=org",
|
||||
"userDnSuffix" : "ou=People,dc=keycloak,dc=org",
|
||||
"bindDn" : "uid=admin,ou=system",
|
||||
"bindCredential" : "secret",
|
||||
"kerberosRealm" : "KEYCLOAK.ORG",
|
||||
"serverPrincipal" : "HTTP/localhost@KEYCLOAK.ORG",
|
||||
"keyTab" : "/tmp/http.keytab"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<name>Keycloak Examples - Kerberos Credential Delegation</name>
|
||||
<artifactId>examples-kerberos</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<description>
|
||||
Kerberos Credential Delegation Example
|
||||
</description>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jboss</id>
|
||||
<name>jboss repo</name>
|
||||
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>kerberos-portal</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package org.keycloak.example.kerberos;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import javax.security.sasl.Sasl;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.constants.KerberosConstants;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.KerberosSerializationUtils;
|
||||
|
||||
/**
|
||||
* Sample client able to authenticate against ApacheDS LDAP server with Krb5 GSS Credential.
|
||||
*
|
||||
* Credential was previously retrieved from SPNEGO authentication against Keycloak auth-server and transmitted from
|
||||
* Keycloak to the application in OIDC access token
|
||||
*
|
||||
* We can use GSSCredential to further GSS API calls . Note that if you will use GSS API directly, you can
|
||||
* attach GSSCredential when creating GSSContext like this:
|
||||
* GSSContext context = gssManager.createContext(serviceName, krb5Oid, deserializedGssCredFromKeycloakAccessToken, GSSContext.DEFAULT_LIFETIME);
|
||||
*
|
||||
* In this example we will authenticate with GSSCredential against LDAP server, which calls GSS API under the hood
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GSSCredentialsClient {
|
||||
|
||||
public static LDAPUser getUserFromLDAP(HttpServletRequest req) throws Exception {
|
||||
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) req.getUserPrincipal();
|
||||
AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken();
|
||||
String username = accessToken.getPreferredUsername();
|
||||
|
||||
// Retrieve kerberos credential from accessToken and deserialize it
|
||||
String serializedGssCredential = (String) keycloakPrincipal.getKeycloakSecurityContext().getToken().getOtherClaims().get(KerberosConstants.GSS_DELEGATION_CREDENTIAL);
|
||||
GSSCredential gssCredential = KerberosSerializationUtils.deserializeCredential(serializedGssCredential);
|
||||
|
||||
// First try to invoke without gssCredential. It should fail
|
||||
try {
|
||||
invokeLdap(null, username);
|
||||
throw new RuntimeException("Not expected to authenticate to LDAP without credential");
|
||||
} catch (NamingException nse) {
|
||||
System.out.println("GSSCredentialsClient: Expected exception: " + nse.getMessage());
|
||||
}
|
||||
|
||||
return invokeLdap(gssCredential, username);
|
||||
}
|
||||
|
||||
private static LDAPUser invokeLdap(GSSCredential gssCredential, String username) throws NamingException {
|
||||
Hashtable env = new Hashtable(11);
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
env.put(Context.PROVIDER_URL, "ldap://localhost:10389");
|
||||
|
||||
if (gssCredential != null) {
|
||||
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
|
||||
env.put(Sasl.CREDENTIALS, gssCredential);
|
||||
}
|
||||
|
||||
DirContext ctx = new InitialDirContext(env);
|
||||
try {
|
||||
Attributes attrs = ctx.getAttributes("uid=" + username + ",ou=People,dc=keycloak,dc=org");
|
||||
String uid = username;
|
||||
String cn = (String) attrs.get("cn").get();
|
||||
String sn = (String) attrs.get("sn").get();
|
||||
return new LDAPUser(uid, cn, sn);
|
||||
} finally {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class LDAPUser {
|
||||
|
||||
private final String uid;
|
||||
private final String cn;
|
||||
private final String sn;
|
||||
|
||||
public LDAPUser(String uid, String cn, String sn) {
|
||||
this.uid = uid;
|
||||
this.cn = cn;
|
||||
this.sn = sn;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public String getCn() {
|
||||
return cn;
|
||||
}
|
||||
|
||||
public String getSn() {
|
||||
return sn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"realm" : "kerberos-demo",
|
||||
"resource" : "kerberos-app",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "/auth",
|
||||
"ssl-required" : "external",
|
||||
"enable-basic-auth" : "true",
|
||||
"credentials": {
|
||||
"secret": "password"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>kerberos-portal</module-name>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>KerberosApp</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>user</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<!--
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<user-data-constraint>
|
||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||
</user-data-constraint>
|
||||
</security-constraint> -->
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>does-not-matter</realm-name>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
</web-app>
|
||||
@@ -0,0 +1,37 @@
|
||||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1" %>
|
||||
<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
|
||||
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
|
||||
<%@ page import="org.keycloak.example.kerberos.GSSCredentialsClient" %>
|
||||
<%@ page import="org.keycloak.example.kerberos.GSSCredentialsClient.LDAPUser" %>
|
||||
<%@ page session="false" %>
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Kerberos Credentials Delegation Example</title>
|
||||
</head>
|
||||
<body bgcolor="#ffffff">
|
||||
<h1>Kerberos Credentials Delegation Example</h1>
|
||||
<hr />
|
||||
|
||||
<%
|
||||
String logoutUri = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", "/kerberos-portal").build("kerberos-demo").toString();
|
||||
%>
|
||||
<b>List of users from LDAP</b> | <a href="<%=logoutUri%>">Logout</a><br />
|
||||
<hr />
|
||||
<%
|
||||
try {
|
||||
GSSCredentialsClient.LDAPUser ldapUser = GSSCredentialsClient.getUserFromLDAP(request);
|
||||
out.println("<p>uid: <b>" + ldapUser.getUid() + "</b></p>");
|
||||
out.println("<p>cn: <b>" + ldapUser.getCn() + "</b></p>");
|
||||
out.println("<p>sn: <b>" + ldapUser.getSn() + "</b></p>");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
out.println("<b>There was a failure invoking LDAP. Check server.log for more details</b>");
|
||||
}
|
||||
%>
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,5 +33,6 @@
|
||||
<module>multi-tenant</module>
|
||||
<module>basic-auth</module>
|
||||
<module>fuse</module>
|
||||
<module>kerberos</module>
|
||||
</modules>
|
||||
</project>
|
||||
Reference in new issue
Block a user