I need some help delegating user authentication in my spring-based application to Active Directory that seems to be delegating this responsibility to Kerberos - I can't seem to figure out how to do this. Here is more of what the mess really looks like:
I followed Spring guide on configuring Spring Security to work with an LDAP server. It went fine.
I got host, port of my actual LDAP server. I configure Spring Security to talk to it, it won't allow me to authenticate.
OK, I download jxplorer and connect to my LDAP server with it successfully. If jxplorer can connect to LDAP only knowing HOST, PORT, USERNAME, and PASSWORD, I figure my application should be able to do the same.
Weirdly, my LDAP does not show an OU=people. My people are scattered deeper in the tree among various OUs, an OU per department kind of way - but, most people are found equally deep inside the tree.
Also WEIRDLY, my actual people nodes that uniquely identify a person have no userPassword attribute.
For experimentation, I configure Spring Security in such a way that it tries to authenticate an individual by looking in the OU that represents my department and tell it to use as password mailNickname (using PlaintTextPasswordEncoder()) and it works fine - only on port 3268, not on 389.
At this point I start speculating - LDAP, is telling my spring-based app that it needs to talk to Kerberos, and I did not tell it how to do that, so that explains why my app fails to authenticate. BUT, no one told jxplorer that Kerberos will authenticate it and yet it managed to get a view of the LDAP tree. Clearly, my spring-app's assumptions != jxplorer's assumptions. I give them the exact same info yet one manages to authenticate the other not. Anyone any idea?
EDIT:
ok, so, I still do not have this solved, but my error has changed and that is a mark of progress, I suppose.
I configured spring according to this: link
Now, when I try to log in, if I give a wrong password or username, I get the complaint that clearly indicates that password is given wrong. However, if I give the correct password, the complaint differs.
The end of stack trace includes:
Caused by: javax.security.auth.login.LoginException: Pre-authentication information was invalid (24)
But, the debug also indicates that the user is found in kerberos database because it says: "principal is username#correct_realm" and "Added server's keyKerberos Principal correct_user#correct_realm" and does some hex dump.
Because of that, I am sure that my keytab is not doing its job. I am certain that my key tab is found by Spring because it says: KeyTab is my_keytab_file (otherwise it'd say: KeyTab is null).
If it is configured with Kerberos, you can try SPNEGO. It's one of the best libraries around for Kerberos in Java.
READ: http://spnego.sourceforge.net/
Sample code for Kerberos Auth:
Example usage (username/password):
public static void main(final String[] args) throws Exception {
System.setProperty("java.security.krb5.conf", "krb5.conf");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("java.security.auth.login.config", "login.conf");
SpnegoHttpURLConnection spnego = null;
try {
spnego = new SpnegoHttpURLConnection("spnego-client", "dfelix", "myp#s5");
spnego.connect(new URL("http://medusa:8080/index.jsp"));
System.out.println(spnego.getResponseCode());
} finally {
if (null != spnego) {
spnego.disconnect();
}
}
}
Related
I am trying to secure Confluent Control Center 7.2.2 with the jetty LdapLoginModule. I have the following jaas configuration working.
c3 {
org.eclipse.jetty.jaas.spi.LdapLoginModule required
useLdaps="true"
contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
hostname="ldaps.xxxx.xxxxx"
port="xxx"
bindDn=<user principal name>
bindPassword=<user password>
authenticationMethod="simple"
forceBindingLogin="true"
userBaseDn="DC=xxxx,DC=xxxx,DC=xxx,DC=xx"
userRdnAttribute="userPrincipalName"
userIdAttribute="userPrincipalName"
userObjectClass="user"
roleBaseDn="OU=xxxxxx,OU=xxx,OU=xxxxx,DC=xxxx,DC=xxxx,DC=xxx,DC=xx"
roleNameAttribute="cn"
roleMemberAttribute="member"
roleObjectClass="group";
};
I would like to avoid passing a bindDn and bindPassword and use the authenticating user credentials to bind instead. My understanding is that forceBindingLogin set to true should make that possible.
forceBindingLogin
Indicate whether to bind as the user that is authenticating (true), otherwise bind as the manager and perform a search to verify user password (false).
Although when I remove bindDn and bindPassword from my config I get the following error:
DAP: error code 1 - 000004DC: LdapErr: DSID-0C090A71, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v3839
It looks like bindDn is still used when forceBindingLogin is set to true.
I noticed that in the Confluence documentation, the bindDn config went from optional to required between 6.1.9 and 6.2.0. Jetty was upgraded to 9.4.39, but there is no mention as to why bindDn would now be required.
I don't have experience with Confluent Control Center, but have been dealing with a lot of Java applications and OAuth that uses LDAP authentication during the years. As a DevOps I had to also manage a few LDAP instances myself so I will be rather speaking from that background.
The authentication process with OpenLDAP requires one of the following to be true:
Allows anonymous (connection) binding
This configuration allows you to connect to the LDAP server as an anonymous user and lookup any object in the tree and therefore find any groups, users, etc.
Manager/User dn (connection) binding
This configuration mandates you to connect to the LDAP server as a pre-configured user that has access to lookup objects in the tree and find the requested groups,users,etc. You can have many Manager DNs configured to access different parts of the LDAP tree.
Notice I mentioned CONNECTION binding - think of this as how you would connect to Postgres or MySQL. You need to have some credentials in order to lookup tables or if anonymous is enabled you can do pretty much anything.
The same thing applies to LDAP servers -- anonymous binding will allow you to establish a connection and lookup anything and manager / user dn requires authentication before the connection can be established.
People are not always deeply aware of LDAP's architecture and the documentation doesn't really help there. As such, the terminology makes you jump to the rational conclusion you jumped to:
#alex: I would like to avoid passing a bindDn and bindPassword and use the authenticating user credentials to bind instead
This would work only when the LDAP server is configured to allow Anonymous connections. If it is configured to require Manager/User DN connection:
When you bind as the user the authentication will likely pass (unless LDAP is configured not to allow that at all).
Once it succeeds it you would attempt to lookup the authorization objects (roles / groups / etc). At this point it will fail due to the inability to do the needed lookups and finally the result will be failed login.
To validate if this is the case you are ending in you should:
Use some tool to the LDAP server (you can try Apache Directory Studio
Setup a connection with binds as the bespoken user (i.e. my-username; not the manager dn, not a read-only dn)
Once you are connected try to lookup the userBaseDn and roleBaseDn DN's -- you are most likely not going to see any of the roles.
If the above is true then what you want is not possible with the current LDAP server setup.
If not -- then the LDAP server truly allows you to bind as the user and to lookup the directory tree. In this case you should open up a bug report with Confluent.
I am trying to create a Java desktop app, that would require login to Windows Active Directory Domain for its users.
However, because computers that this app would be launched on, already are in said domain, I thought it is not the best solution to provide credentials to login to computer and moments later, same credentials for the app.
That is why I thought of uninteractive Kerberos login, where the app would authenticate as an user logged on the computer without a prompt. Basically, if user logs to the computer (on his domain account), he should not be prompted for credentials when launching the application.
This is the best and simplest solution that works for me so far:
public class GssExample {
public static void main(String[] args) throws LoginException {
Subject mysubject = new Subject();
System.setProperty("java.security.auth.login.config", "jaasprj04.conf");
System.setProperty("java.security.krb5.conf", "krb5.conf");
LoginContext loginContext = new LoginContext("GssExample", mysubject, new TextCallbackHandler());
loginContext.login();
}
}
And the configuration files contents:
jaasprj04.conf:
GssExample {
com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true debug=true;
};
krb5.conf:
[realms]
MYDOMAIN.COM = {
kdc = mydomain.com:88
default_domain = MYDOMAIN.COM
}
However, this solution prompts for password in the command line, and I would like it to use Integrated Windows Authentication or some other mechanism, that would enable it to use credenials of logged user. (That would be Kerberos token, I think)
This article was a huge help to me: https://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/single-signon.html
But I don't think it talks about the case I want, since it describes client and server parts of code. In my case there is only client and Windows Active Directory. My app would log to AD with Kerberos token and perform some operation (for example if I login to app as Domain Administrator, I could add an user to AD through the app). There is no server in between. (I am considering using Keycloak though)
There are some helpful examples for Kerberos Authentication on the Internet, but almost all of them describe web applications that use SPNEGO token and web browser to authenticate.
My app however won't use web browser at all, so it is pretty different from examples i stumbled upon on the Internet.
Is the effect I describe even possible in Java desktop application? If so, how can I achieve it?
Note: I tried the doNotPrompt parameter in conf file and I've made the AllowTGTSessionKey registry change - neither worked for me.
My Mongo server should be set up correctly since I can query against it using GSSAPI mechanism with client.
According to the documentation, Java Driver's Kerberos Authentication can be as simple as
credentialList.add(MongoCredential.createGSSAPICredential("people/myhost.com#EXAMPLE.COM"));
The principal I used
I tested with Mongo's enterprise version of client and it works: authenticated against Mongo server with Kerberos and can find() against it. (database test, principal "people/myhost.com#EXAMPLE.COM")
kinit performed and the new ticket is showed in the klist, ticket cached stored under KRB5CCNAME=D:\Kerberos\tickets.txt (environment variable set)
To make sure krb5.ini/conf is read, I manually set the system property java.security.krb5.conf=C:/Windows/krb5.conf
Before I set the property javax.security.auth.useSubjectCredsOnly, GSSAPIAuthenticator.createSaslClient() catched exception of GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt).
After I set the property javax.security.auth.useSubjectCredsOnly=false, InternalStreamConnection.open() catched throwable: java.lang.SecurityException: Unable to locate a login configuration
I am really confused here. I thought it is using the ticket cache which is specified under KRB5CCNAME. If I use jaas configuration, what name should I assign it to be?
Name {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
keyTab="D:\\Kerberos\\people.keytab"
useTicketCache=false;
};
I set it with a random name and it started complaining GSSException: No valid credentials provided (Mechanism level: Attempt to obtain new INITIATE credentials failed! (null)).
Can you guys help me on this? What else I can try here or are there more useful and detailed logs that I can enable in this case?
Security error messages are cryptic, by design :-/
But there is a nice "security trace flag" property to help you debug GSSAPI config issues:
-Djava.security.debug=gssloginconfig,configfile,configparser,logincontext
I use the Jinterop library for an access to remote WMI.
JISystem.setAutoRegisteration(true);
JISession session = JISession.createSession(System.getenv("USERDOMAIN"), login, password);
session.useSessionSecurity(true);
final JIComServer server = new JIComServer(JIProgId.valueOf(WBEM_PROGID), HOST, session);
I want to use impersonation for accessing with current user and password to remote machine. But when I use empty login and password, I always get exception
jcifs.smb.SmbAuthException: Logon failure: unknown user name or bad password.
at jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:546)
Is this possible to use impersonation in Jinterop?
I am not 100% sure, but I have worked with j-interop for some time and I have never seen or read about this feature, so I don't think that this is possible. In my opinion you always have to specify the credentials.
Further, I am curious if this is really working for you?:
JISystem.setAutoRegisteration(true);
This means that j-interop will automatically try to modify values/keys in the registry as needed. In my experience, in case you want to access the WbemScripting.SWbemLocator class, due to tighter security constraints (the necessary keys are owned by the 'TrustedInstaller' user) this automatic modification is not possible any more starting from Windows Vista and above. I always had to set this to false and modify the values/keys manually in the registry.
i'm trying to make an application with the Keberos protocol and the GSS-API in Java, and i've already made the authentication and the context establishement before calling the doAsPrivileged method.
In this method I get the mutual authentication sending a simple token from the client to the server, but after that i want to make some other things.
I want to open a new window with a table of products to let the client select them and buy something and that was connected to a database in the server.
my question is about how can a i use this context in other frames that are diferent from the original doAsPrivileged action class.
i get an error GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt) and i don't know how can i find this TGT to send it more than one time to the server.
thank you.
I ran into a similar issue as well.
Your code fails because Java tries to use GSSAPI with the default login config name. Which is com.sun.security.jgss.initiate. To perform a GSS call for someone else or with another login conf name you have to use the LoginContext, obtain the subject and then do a doAs. As far as I can see, every action involving ticket exchange has to be done in a PrivilegedAction if you don't stick to the defaults. That's why our stuff's failing :-(