29 September 2014

SSO with Fediz IDP and Kerberos

Colm O hEigeartaigh extended Fediz IDP just recently to allow browser-based Kerberos authentication at the IDP. In this blog I'm going to explain how to setup your system environment to provide WS-Federation based SSO for a normal web application:
  • Prepare your Active Directory
  • Installing the Fediz Demo Application
  • Installing Fediz IDP and a Kerberos enabled STS
  • Enable Kerberos for your Browser

Preparing Active Directory

To setup my test environment I needed a Active Directory service providing my test users and Kerberos support. After installing a Windows Server 2008 R2 Enterprise Edition server, I added the Active Directory feature according to a well written post, which I found on the internet.

Adding users and groups

After that I created two new security groups:
  • manager
  • employee
For being able to choose trivial passwords for my test users, I disabled the global password policy as well as the local password policy.

After that I added the following users to my active directory:
    • alice@MYDOMAIN.COM
    • bob@MYDOMAIN.COM
    • idp/idp-host.mydomain.com@MYDOMAIN.COM

      I also added Alice to the group manager and employee, as well as Bob to the group employee only.

      Adding Service Principal Name to the IDP user

      If a browser requests a Kerberos token for a webpage the Kerberos token will always be requested for the following ServicePrincipalName: HTTP/my.idp-website.com.
      Therefore I need to add the matching SPN to my AD user idp/myhost.mydomain.com@MYDOMAIN.COM.

      I my case I had some trouble with the normal account name, but with the pre-Windows2000 name it was working without issues. The pre-Windows200 account name of my idp would be MYDOMAIN\idp_myhost.

      You need to enter the following command on a console at your domain controller:

      C:\> setspn -A HTTP/idp-host.mydomain.com MYDOMAIN\idp_myhost
      C:\> setspn -A HTTP/idp-host MYDOMAIN\idp_myhost

      If your IDP Server is accessible via multiple domain names (only DNS A-Records are considered), you should also add multiple SPNs to your IDP user.

      You can use the following command to validate that you set the correct SPNs:

      C:\> setspn -Q HTTP/*

      You should also make sure that you have a SPN set only once! You can validate this with the following command:

      C:\> setspn -X

      It is very important, that you also add the correct DNS names for your IDP in your Windows DNS Server. You cannot use an IP address directly for the IDP server. If you do so your browser will not request a correct Kerberos ticket for your IDP!

      Extracting service private key from Kerberos


      I used username/password authentication for the IDP user in the STS Kerberos token validator, which does not require to extract a keytab file from your Active Directory server. If you do use this keytab file you do not need to provide username/password instead.

      The STS needs access to its own keytab file for being able to validate the Kerberos authentication of the user. Therefore we need to extract the private key to a file.

      ktpass -princ idp/idp-host.mydomain.com@MYDOMAIN.COM -mapuser MYDOMAIN\IdpMyHost -pass password -crypto All -kvno 0 -ptype KRB5_NT_PRINCIPAL -out c:\temp\IdpMyHost.keytab

      The extracted keytab file must be transfered to the server where the IDP and STS will be installed.

      Installing Demo Application

      Download and setup Apache Tomcat 7 as usual.
       
      After that you must define a fediz_config.xml configuration file for the Fediz plugin in the conf folder from Tomcat. You can find further steps to setup Tomcat on the Apache website.

      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
      <FedizConfig>
          <contextConfig name="/fedizhelloworld">
              <audienceUris>
                  <audienceItem>urn:org:apache:cxf:fediz:fedizhelloworld</audienceItem>
              </audienceUris>
              <certificateStores>
                  <trustManager>
                      <keyStore file="ststrust.jks" password="storepass" type="JKS" />
                  </trustManager>
              </certificateStores>
              <trustedIssuers>
                  <issuer certificateValidation="PeerTrust" />
              </trustedIssuers>
              <maximumClockSkew>1000</maximumClockSkew>
              <protocol xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:type="federationProtocolType" version="1.0.0">
                  <realm>urn:org:apache:cxf:fediz:fedizhelloworld</realm>
                  <issuer>https://localhost:9443/fediz-idp/federation</issuer>
                  <roleDelimiter>,</roleDelimiter>
                  <roleURI>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role</roleURI>
                  <claimTypesRequested>
                      <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role" optional="false" />
                  </claimTypesRequested>
              </protocol>
          </contextConfig>
      </FedizConfig>
      
      
      Now you can download the the latest version of Fediz from github. Next you need to build all Fediz dependencies with maven and copy them to the lib/fediz folder of your Tomcat installation. Go to cxf-fediz/plugins/tomcat and execute the following command:

      mvn clean install

      You must also update the common.loader property in conf/catalina.properties:
      common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,${catalina.home}/lib/fediz/*.jar

      Next you can build the example web application. Go to cxf-fediz/examples/simpleWebapp and build your webapp again with maven:

      mvn clean install

      You must then copy the war file of the example web application to your Tomcat webapps folder.

      Now you can start your relying party Tomcat container.

      If you open http://localhost:8080/fedizhelloworld/ in your browser you should see a "Hello World" message.
      If you go to http://localhost:8080/fedizhelloworld/secure/fedservlet you will be redirected to the IDP which we will setup next.

      Installing Fediz IDP & STS

      Tomcat Setup

      Make sure that the JDK has unlimited security policies installed. Since we already have a running Tomcat 7 installation for the relying party we will just copy that installation and deleting the fedizhelloworld web application from the webapps/ folder as well as the fediz_config.xml configuration file in the conf/ folder. If we plan to run both Tomcat Server on the same machine we must also update the server ports in the conf/server.xml file.
      <Connector port="9443" protocol="org.apache.coyote.http11.Http11Protocol"
           maxHttpHeaderSize="65536"
           maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
           keystoreFile="idp-ssl-server.jks"
           keystorePass="tompass"
           truststoreFile="idp-ssl-server.jks"
           truststorePass="tompass"
           truststoreType="JKS" 
           SSLVerifyClient="optional"
           clientAuth="want"
           sslProtocol="TLS" />
      
      
      It is very important that you increase the default max header size from tomcat. Otherwise some of your users will get a 401 Bad Request Error if the Kerberos token is bigger than 8kb.

      You must also set clientAuth to want and configure a trust-store, if you run IDP and STS in the same Tomcat container, because the STS requires x509 client authentication.

      Since the IDP uses JPA (since version 1.2) to store its configuration settings, we must also download the matching JDBC driver and place the driver to our Tomcat lib folder. Next we must set the domain name or IP address of the KDC Kerberos server. This we can do best by setting a JVM parameter via CATALINA_OPTS by creating or updating the setenv.bat respectively setenv.sh in the Tomcat bin folder:
      set CATALINA_OPTS=%CATALINA_OPTS% "-Djava.security.krb5.kdc=192.168.1.10 -Djava.security.krb5.realm=MYDOMAIN.COM -Djava.security.auth.login.config=file:/C:/Fediz-IDP-A/webapps/fediz-idp/WEB-INF/kerberos.jaas -Dsun.security.krb5.debug=true"
      If you are planing on running your IDP/STS Tomcat container as a system service, you must modify the service.bat file to ensure that you set the required Kerberos JVM parameter correctly. Otherwise your Tomcat will start without these parameters and Kerberos authentication will fail.

      SSL Certificate

      Providing a trusted SSL Certificate for the IDP helps to avoid irritating warnings from the browser when users access the IDP via a HTTPs connection. Since all computer of a Active Directory Domain share common trusted domain certificates, we can create a new webserver key at the AD which will be trusted by all participants of the domain.

      The certificate is only trusted at the Internet Explorer by default. To extend this trust to Firefox as well, you need to install the domain certificate manually into the trusted issuer store of Firefox.

      To do this you must define a certificate webserver policy at the AD server which allows the export of a private key. Next we need to create a new private/public key pair at the server with the common name (CN) being equal to the domain name of our IDP. After creating the certificate we need to export the key pair to a PKCS #12 file format. I like to use the KeyStore Explorer best to manage Java keystores. The KeyStore Explorer is also able to import the keypair export from the AD server. To set the correct key for the IDP Tomcat server, you need to replace the mytomidpkey key in the idp-ssl-server.jks keystore.

      Download or Build the IDP/STS

      You can either download the IDP/STS from Apache webpage or you can build the IDP and STS again with maven. If you build your services from code, you can also update the configuration files before you build your war files. In that case you do not need to modify them later on. If you want to use Maven to build your own services execute the following command: mvn clean install -Pkerberos After deploying the fediz-idp.war and fediz-idp-sts.war to tomcat, you need to update the following configuration files according to your needs.

      STS Configuration

      The kerberos.jaas file must be updated for the STS to match with our Kerberos idp principal:
      idp { 
          com.sun.security.auth.module.Krb5LoginModule required
          refreshKrb5Config=true
          storeKey=true
          principal="idp/idp-host.mydomain.com@MYDOMAIN.COM";
      };
      
      Make sure that the cxf-transport.xml file imports the kerberos.xml file, which should already be the case if you build the STS with the kerberos Maven profile. Most changes required to enable Kerberos Authentication and Active Directory Claim Handling needs to be configured in the kerberos.xml file. You need to add the LDAP Claim Handler as well as the Kerberos Token Validator. If you use a kerberos keytab file for the IDP you do not need to set a callbackHandler for the kerberos token validator but the location to your keytab file instead.
      <?xml version="1.0" encoding="UTF-8"?>
      <!--
        Licensed to the Apache Software Foundation (ASF) under one
        or more contributor license agreements. See the NOTICE file
        distributed with this work for additional information
        regarding copyright ownership. The ASF licenses this file
        to you under the Apache License, Version 2.0 (the
        "License"); you may not use this file except in compliance
        with the License. You may obtain a copy of the License at
       
        http://www.apache.org/licenses/LICENSE-2.0
       
        Unless required by applicable law or agreed to in writing,
        software distributed under the License is distributed on an
        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        KIND, either express or implied. See the License for the
        specific language governing permissions and limitations
        under the License.
      -->
      <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:util="http://www.springframework.org/schema/util"
          xmlns:jaxws="http://cxf.apache.org/jaxws"
          xsi:schemaLocation="
              http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
              http://www.springframework.org/schema/util
              http://www.springframework.org/schema/util/spring-util-2.0.xsd
              http://cxf.apache.org/jaxws
              http://cxf.apache.org/schemas/jaxws.xsd">
      
       <util:list id="claimHandlerList">
         <ref bean="claimsHandlerA" />
         <ref bean="claimsHandlerB" />
       </util:list>
       
       <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
         <property name="url" value="ldap://ad-host.mydomain.com:389" />
         <property name="userDn"
        value="CN=IDP,OU=People,DC=mydomain,DC=com" />
         <property name="password" value="secretPwd" />
       </bean>
       
       <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
         <constructor-arg ref="contextSource" />
       </bean>
      
       <util:map id="claimsToLdapAttributeMapping">
         <entry key="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role" value="memberOf" />
         <entry key="http://schemas.zurich.com/security/authorization/de/2013/05/claims/role" value="memberOf" />
         <entry key="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" value="givenName" />
         <entry key="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" value="sn" />
         <entry key="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" value="mail" />
         <entry key="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country" value="c" />
       </util:map>
        
       <bean id="claimsHandlerA" class="org.apache.cxf.sts.claims.LdapClaimsHandler">
         <property name="ldapTemplate" ref="ldapTemplate" />
         <property name="claimsLdapAttributeMapping" ref="claimsToLdapAttributeMapping" />
         <property name="userNameAttribute" value="userPrincipalName" />
         <property name="userBaseDN" value="OU=People,DC=mydomain,DC=com" />
         <property name="realm" value="REALMA" />
       </bean>
       
       <bean id="kerberosValidator" class="org.apache.ws.security.validate.KerberosTokenValidator">
        <property name="contextName" value="idp"/>
        <property name="serviceName" value="HTTP/idp-host.mydomain.com@MYDOMAIN.COM"/>
        <property name="usernameServiceNameForm" value="true"/>
        <property name="spnego" value="true"/>
        <property name="callbackHandler">
           <bean class="com.sun.jndi.ldap.sasl.DefaultCallbackHandler">
           <constructor-arg index="0" value="idp/idp-host.mydomain.com@MYDOMAIN.COM"/>
           <constructor-arg index="1" value="secretPwd"/>
           <constructor-arg index="2" value="MYDOMAIN.COM"/>
           </bean>
        </property>
       </bean>
      
       <jaxws:endpoint id="transportSTSRealmAKerberos"
        implementor="#transportSTSProviderBean" address="/REALMA/STSServiceTransportKerberos"
        wsdlLocation="/WEB-INF/wsdl/ws-trust-1.4-service.wsdl"
        xmlns:ns1="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
        serviceName="ns1:SecurityTokenService" endpointName="ns1:TransportKerberos_Port">
        <jaxws:properties>
         <entry key="ws-security.bst.validator" value-ref="kerberosValidator"/>
        </jaxws:properties>
       </jaxws:endpoint>
      
       <jaxws:endpoint id="transportSTSRealmBKerberos"
        implementor="#transportSTSProviderBean" address="/REALMB/STSServiceTransportKerberos"
        wsdlLocation="/WEB-INF/wsdl/ws-trust-1.4-service.wsdl"
        xmlns:ns1="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
        serviceName="ns1:SecurityTokenService" endpointName="ns1:TransportKerberos_Port">
        <jaxws:properties>
         <entry key="ws-security.bst.validator" value-ref="kerberosValidator"/>
        </jaxws:properties>
       </jaxws:endpoint>
      
      </beans>
      

      IDP Configuration

      The content of the security-config.xml file must be replaced with the content of security-config-kerberos.xml:
      <?xml version="1.0" encoding="UTF-8"?>
      <!--
        Licensed to the Apache Software Foundation (ASF) under one
        or more contributor license agreements. See the NOTICE file
        distributed with this work for additional information
        regarding copyright ownership. The ASF licenses this file
        to you under the Apache License, Version 2.0 (the
        "License"); you may not use this file except in compliance
        with the License. You may obtain a copy of the License at
       
        http://www.apache.org/licenses/LICENSE-2.0
       
        Unless required by applicable law or agreed to in writing,
        software distributed under the License is distributed on an
        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        KIND, either express or implied. See the License for the
        specific language governing permissions and limitations
        under the License.
      -->
      <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:security="http://www.springframework.org/schema/security"
          xmlns:context="http://www.springframework.org/schema/context"
          xsi:schemaLocation="
              http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
              http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
              http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
      
          <context:property-placeholder location="classpath:realm.properties"/>
          <context:component-scan base-package="org.apache.cxf.fediz.service.idp"/>
          
          <!-- Configure Spring Security -->
          <security:http auto-config="false" use-expressions="true" entry-point-ref="kerberosEntryPoint">
              <security:custom-filter after="CHANNEL_FILTER" ref="stsPortFilter" />
              <security:intercept-url pattern="/FederationMetadata/2007-06/FederationMetadata.xml" access="isAnonymous() or isAuthenticated()" />
      
              <!--<security:http-basic />-->
              <!--<security:form-login />-->
              <security:custom-filter ref="kerberosAuthenticationProcessingFilter" position="BASIC_AUTH_FILTER" />
          </security:http>
          
          <bean id="kerberosEntryPoint"
                class="org.apache.cxf.fediz.service.idp.kerberos.KerberosEntryPoint" />
          
          <bean id="kerberosAuthenticationProcessingFilter"
                class="org.apache.cxf.fediz.service.idp.kerberos.KerberosAuthenticationProcessingFilter">
                <property name="authenticationManager" ref="authenticationManager" />
          </bean>
      
          <security:authentication-manager alias="authenticationManager">
              <security:authentication-provider ref="stsAuthProvider" />
          </security:authentication-manager>
       
          <bean id="stsPortFilter" class="org.apache.cxf.fediz.service.idp.STSPortFilter" />
       
          <bean id="stsAuthProvider" class="org.apache.cxf.fediz.service.idp.STSAuthenticationProvider">
              <property name="wsdlLocation" value="https://localhost:0/fediz-idp-sts/${realm.STS_URI}/STSServiceTransportKerberos?wsdl"/>
              <property name="wsdlEndpoint" value="TransportKerberos_Port"/>
              <property name="wsdlService" value="SecurityTokenService"/>
              <property name="appliesTo" value="urn:fediz:idp"/>
              <property name="tokenType" value="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"/>
          </bean>
      
      </beans>
      

      Enabling Kerberos Support for your browser

      By default your browser will not send any Kerberos token to a normal Webpage. In IE you need to add the IDP URL to the list of trusted sides or even to local intranet sides. In Firefox you need to open about:config and add the IDP URL to network.negotiate-auth.delegation-uris and network.negotiate-auth.trusted-uris. I found this article from Oracle most helpful to setup my browser correctly.

      Troubleshooting

      You can use the klist command at your client to verify that the correct Kerberos token are used on your local machine. klist purge will remove all cached Kerberos tokens on your computer. You can use Wireshark to analyse your web traffic. This is especially helpful to see what kind of token Kerbeidp-ssl-server.jksros/SPNEGO your browser is sending to the IDP. Fiddler is helpful to analyze if all the redirects from your Webapp to the IDP and back actually work as expected.

       Links

      • http://docs.fedoraproject.org/en-US/Fedora/html/Security_Guide/sect-Security_Guide-Single_Sign_on_SSO-Configuring_Firefox_to_use_Kerberos_for_SSO.html
      • http://superuser.com/questions/5161/windows-domain-authentication-with-firefox
      • http://www-01.ibm.com/support/knowledgecenter/SSCKBL_8.5.5/com.ibm.websphere.nd.multiplatform.doc/ae/tsec_SPNEGO_config_dc.html
      • http://blogs.iis.net/brian-murphy-booth/archive/2007/03/09/the-biggest-mistake-serviceprincipalname-s.aspx
      • http://www.novell.com/documentation/novellaccessmanager/adminguide/data/b9uctt5.html
      • http://www.oracle.com/technetwork/articles/idm/weblogic-sso-kerberos-1619890.html

      1 comment:

      1. You can use the following command to test if your kerberos settings are correct and if you can get a service ticket for the defined service:

        kinit -S HTTP/my-web-server@EXAMPLE.COM user@EXAMPLE.COM

        ReplyDelete