Sunday, March 3. 2013
SPNEGO/Kerberos in JavaEE (spnego.java.net)
Two previous entries of the blog were dedicated to the SPNEGO/Kerberos login. The first one just used the Java Filter SPNEGO project to perform a silent login using windows native authentication. The second one extended the solution adding the group SIDs to the principal using the Microsoft PAC extension. Remember that the solution for the second entry consisted in a horrible JAR which used several other projects (or part of projects) like SPNEGO, BouncyCastle, jaaslounge or Spring to read the SIDs. Finally I had the time to perform a better and cleaner integration.
Instead of the SPNEGO filter that I have used in the two entries I changed to the spnego.java.net project. This project is a standard ServerAuthModule for Glassfish that performs the SPNEGO protocol and injects the principal (it works in a way very similar to the SPNEGO filter). But as it is integrated more deeply in the application server, group principals are much easier to integrate. The last version 1.1 of the library is two years old (it seems that that is a common rule for all the projects related to SPNEGO in Java).
The first thing I did was installing the module in glassfish and modifying the hello application to use it. Following the installation instructions the task was done smoothly. Although the documentation always talks abouts v2 it also runs in v3 with no change (cos ServerAuthModule is a standard interface in JavaEE some kind of integration is assured). At the end, the Kerberos principal was injected in the hello WAR application as in the first entry of this series.
If you check the code of the ExampleSpnegoServerAuthModule, the class overrides the getGroupsForCaller method to inject two hardcoded group names (authenticated and allowed). So in order to add the real group SIDs from PAC that method should be overridden. Besides I decided to not use Bouncy Castle ASN.1 implementation (it is an enormous JAR I wanted to avoid) and restrict to the minimum the jaaslounge classes (do you remember my frankenjar of the previous entry?). So in summary the main points of the new implementation are the following:
The getGroupsForCaller method was changed to receive more arguments (original abstract method only had the Principal as argument). In order to parse the PAC more inputs are needed. So I decided to include everything in the method:
public abstract String[] getGroupsForCaller(MessageInfo messageInfo, GSSContext gssContext) throws AuthException;
Now the method receive the MessageInfo and the GSSContext, which has just validated the Kerberos token. This way any extension class will have all the needed inputs.
A new PACSpnegoServerAuth class was implemented. It obviously extends the SpnegoServerAuthModule and implements the new version of the getGroupsForCaller method.
The ASN.1 parsing uses the internal sun.security.util package. Take into account that as DerInputStream or DerValue classes are internal they can change (for example in a future version or in other JavaSE implementations -IBM for example-). Although this decision is not perfect I prefer it better than the Bouncy Castle option.
Part of the kerberos data should be decrypted to get the PAC and, for that, the server keys are needed. In turn a logged subject is needed to get the keys. So the new module performs a login at initialization (using defined krb5 login module) and a logout at finalize (there is no dispose or similar method in the ServerAuthModule interface so I was forced to add it in the finalize method).
Finally only a few classes (six) of the jaaslounge project were added, only the classes that deals with the PAC, more specifically the login info part (where the groups SIDs are). I renamed the package to net.java.spnego.pac and did some minor changes.
The module keeps on just parsing the token in the first access and storing the group SIDs in the Java session, where they are retrieved from in the following requests.
With all these changes the final library only depends in Sun JavaSE (remember the sun.security.util utilization) and commons-codec. And the beautiful result of all this work is that now the SIDs can be mapped in web.xml and glassfish-web.xml configuration files. For example the new hello application web.xml defines some groups as roles:
<security-role> <role-name>Domain Users</role-name> </security-role> <security-role> <role-name>Domain Admins</role-name> </security-role> <security-role> <role-name>group1</role-name> </security-role> <security-role> <role-name>group2</role-name> </security-role> <security-role> <role-name>group3</role-name> </security-role>
And only users in the domain can access the application:
<security-constraint> <web-resource-collection> <web-resource-name>Authentication Required</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>Domain Users</role-name> </auth-constraint> </security-constraint>
But the new solution lets us map the SIDs in the glassfish-web.xml (proprietary) file:
<security-role-mapping> <role-name>Domain Users</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-513</group-name> </security-role-mapping> <security-role-mapping> <role-name>Domain Admins</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-512</group-name> </security-role-mapping> <security-role-mapping> <role-name>group1</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-1106</group-name> </security-role-mapping> <security-role-mapping> <role-name>group2</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-1107</group-name> </security-role-mapping> <security-role-mapping> <role-name>group3</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-1108</group-name> </security-role-mapping>
The group (or user) SIDs can be obtained using WMIC command in the Windows machine:
> wmic group where "name='Domain Users'" Caption Description Domain InstallDate LocalAccount Name SID SIDType Status KVM\Domain Users All domain users KVM FALSE Domain Users S-1-5-21-2145774160-2038213957-1596523949-513 2 OK > wmic group where "name='group1'" Caption Description Domain InstallDate LocalAccount Name SID SIDType Status KVM\group1 KVM FALSE group1 S-1-5-21-2145774160-2038213957-1596523949-1106 2 OK > wmic useraccount where "name='ricky'" AccountType Caption Description Disabled Domain FullName InstallDate LocalAccount Lockout Name PasswordChangeable PasswordExpires PasswordRequired SID SIDType Status 512 KVM\ricky FALSE KVM FALSE FALSE ricky TRUE TRUE TRUE S-1-5-21-2145774160-2038213957-1596523949-1104 1 OK
And that's all, now the video presents how the user ricky accesses to the new hello application and standard isUserInRole is used (the beautiful mapped name is passed as argument, not the horrible SID).
Today's entry improves the spenego.java.net project to add the SIDs as user groups inside a new ServerAuthModule. The new module is the PACSpnegoServerAuth class. It requires a little modification in the definition of the abstract method getGroupsForCaller (more arguments). Besides I tried to minimize the dependencies of previous solutions and now it only depends on commons-codec (original version 1.1 already depended on that and I did not want to change it). ASN.1 parsing is performed with the internal sun.security.util JavaSE package (take care when other JavaSE implementations is used). Six classes from jaaslounge decoding were integrated in the JAR with minimal changes. The hello-spnego-java-net sample application project and the spnego-java-net new library project can be downloaded from the corresponding links. The whole implementation was not very tested but, with no doubt, it is much better than the one presented in the previous entry. What do you think? Do I have to email to the dev@spnego.java.net list explaining my changes?
Comments below!
Comments