KEYCLOAK-1066 Kerberos credential delegation support

This commit is contained in:
mposolda committed 2015-03-06 12:59:29 +01:00
1 parent 8cf31bd3f1
commit db07d79009
42 files changed
+1189 -89

No files matched your search

+66
View File
@@ -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.
+94
View File
@@ -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"
}
}
]
}
+83
View File
@@ -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>
@@ -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>
+1
View File
@@ -33,5 +33,6 @@
<module>multi-tenant</module>
<module>basic-auth</module>
<module>fuse</module>
<module>kerberos</module>
</modules>
</project>