Saturday, April 29, 2017

Authorization of Tomcat users by client certificates & LDAP

Purpose:


We want to achieve the same aim as in the "Authorization of Tomcat clients by Kerberos and LDAP" article, but with some difference: we want user to authenticate with client certificate rather than with Kerberos ticket.
Still, we want to query Active Directory by LDAP for the user's groups (which will map to Tomcat roles), and we want to utilize Kerberos to authenticate Tomcat to LDAP.

(We could also combine these two technics: require user's cert to establish SSL channel, then get his Kerberos ticket via SPNEGO and query LDAP by UPN from the ticket, as described here. Add to this "strong protection", requiring password for the private key, and you'll get 3-factor authentication. :-) )


Prerequisites:

  • Tomcat server should have a certificate with its name in Subject or SAN fields and with "Server Authentication" in "Enhanced Key Usage" field. It should be stored in Personal store of the account used to execute Tomcat, and its private key should be kept in CryptoAPI CSP, not in newer CNG KSP (Java doesn't support these yet).
  • It should also have a CA certificate in its Personal store (without private key) - it will be used to tell the user, certificates issued by which CAs will be accepted.
  • Tomcat should also have a keytab file, created with password of legitimate LDAP user - it will be used to bind to LDAP.
  • Tomcat should have ldaptive​ JAR in its lib folder.
  • The user should have a client certificate with his LDAP Distinguished Name (and nothing else - no e-mail, for instance) in Subject, and with "Client Authentication" in "Enhanced Key Usage" field.​


Tomcat configuration:

server.xml file:

We need to add this HTTPS-enabled Connector:
​<Connector
port="443"
protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150"
SSLEnabled="true"
scheme="https"
secure="true"
sslProtocol="TLS" 
keystoreProvider="SunMSCAPI"
keystoreType="Windows-MY"
keyAlias="website.hosting.org"
keystoreFile=""
truststoreProvider="SunMSCAPI"
truststoreType="Windows-MY"
truststoreFile=""
clientAuth="true" 
/>   
​Notes:
  • keystoreProvider="SunMSCAPI"truststoreProvider="SunMSCAPI" - tells Tomcat to work with Windows certificates stores.
  • keystoreType="Windows-MY" - tells it to search for server's certificate + private key in the Personal store of account running Tomcat.
  • truststoreType="Windows-MY"​ - tells it where to enumerate CAs of the acceptable client certificates. Points to Personal store as well. We could also set truststoreType="Windows-ROOT"​​ as well - this will point to "Trusted Certification Authorities" store, but it lists too many CAs.
  • keystoreFile="", ​truststoreFile=""​ - needed to avoid error, although these variables are of no use: we don't use any JKS or PFX keystores.
  • keyAlias - name of the server's certificate. May be either CN in Subject, or a "friendly name" set in certificate's Properties. "Friendly name" has priority over CN.
This is the only part needed for authentication; all others are for LDAP groups-based ​authorization.

JVM variables:

Tomcat should be started with these Java parameters:

​-Djava.security.krb5.conf=%CATALINA_HOME%\conf\krb5.ini

-Djava.security.auth.login.config=%CATALINA_HOME%​\conf\jaas.conf

-Djavax.security.auth.useSubjectCredsOnly=false

  • java.security.krb5.conf - tells where to find Kerberos configuration file.
  • java.security.auth.login.config - tells where to find config file for JAAS login modules.
  • javax.security.auth.useSubjectCredsOnly=false - makes Tomcat to authenticate to LDAP with its own credentials (keytab file), rather than with user's.


web.xml file:

Can be set per webapp, in WEB-INF folder, or per whole Tomcat server, in %CATALINA_HOME%\conf.
Should contain settings like these:
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Entire App</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>Webapp-Users/role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>CLIENT-CERT</auth-method>
    <realm-name></realm-name>
</login-config>
<security-role>
    <role-name>Webapp-Users</role-name>
</security-role>


Here I'm requiring that access to all URLs of specific webapp or the whole server will be given only to users having Webapp-Users role (in fact, which are members of Webapp-Users group).


context.xml file:

Can be set per webapp, in META-INF folder, or in %CATALINA_HOME%\conf\[enginename]\[hostname], or per whole Tomcat server, in %CATALINA_HOME%\conf, as described here.
Should include these lines:

​<Valve className="org.apache.catalina.authenticator.SSLAuthenticator" alwaysUseSession="true"/>
<Realm className="org.apache.catalina.realm.JAASRealm" appName="ldaptive" userClassNames="org.ldaptive.jaas.LdapPrincipal" roleClassNames="org.ldaptive.jaas.LdapRole" stripRealmForGss="false"/>    


Notes:
  • We could not include the SSLAuthenticator explicitly, because CLIENT-CERT auth-method in web.xml causes this Valve to be used automatically - but we need it for alwaysUseSessionparameter. This parameter causes Tomcat to provide a cookie to the user, which will use it in subsequent calls, instead of quering LDAP on each user's HTTP request.
  • appName="ldaptive"​ - defines to JAASRealm, which section of JAAS configuration file (jaas.conf) to use.
  • userClassNames​ - defines which class will be used to store user's name.
  • roleClassNames - defines instances of which class will be used to store user's groups (a.k.a "roles").
  • stripRealmForGss="false" means that user's name will be in "username@ourcompany.com" format, not just "username". We'll want this in cases where our Active Directory domain has trust relationships​ with other domains (in which the "username" can be associated with another account). The short format doesn't guarantee unique names, the long one does.

jaas.conf file:

com.sun.security.jgss.initiate
{
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt="true"
    principal="HTTP/website.hosting.org@OURCOMPANY.COM"
    useKeyTab="true"
    debug=false
    keyTab="../conf/ccs-keytab";
};
ldaptive
{
 org.ldaptive.jaas.LdapRoleAuthorizationModule required
    javax.security.sasl.server.authentication="true"
    ldapUrl="ldap://dc01.ourcompany.com:3268 ldap://dc02.ourcompany.com:3268"
    connectionStrategy="ACTIVE_PASSIVE"
    searchEntryHandlers="org.ldaptive.handler.RecursiveEntryHandler{{searchAttribute=memberOf}{mergeAttributes=cn}}"
    bindSaslConfig="{mechanism=GSSAPI}"
    baseDn="DC=OURCOMPANY,DC=COM"
    subtreeSearch="true"
    roleFilter="(&(member={user})(objectClass=group))"
    roleAttribute="cn";
};​
Notes:
  • The ldaptive section tells ldaptive​ that we know DN of the user (it will be obtained by SSLAuthenticator from the client cert) and want to get groups the user is member of.
  • The ​com.sun.security.jgss.initiate section is used, when ldaptive authenticates by Kerberos to LDAP. It tells where the keytab file is located.

krb5.ini file:

This is used to speed up discovery of Kerberos Key Distribution Center, KDC (normally domain controller):

[libdefaults]  
default_realm = OURCOMPANY.COM

[realms]
COMPANY.COM = {
             kdc = dc01.ourcompany.com
}

[domain_realm]
.ourcompany.com = OURCOMPANY.COM
ourcompany.com  = OURCOMPANY.COM​ 

No comments:

Post a Comment