I'm trying to connect to ldap server using SASL. I'm connecting using url ldaps://ldap.example.com but server hostname is host.example.com. ldap.example.com is cname for host.example.com. My program is trying to get service ticket for ldap/ldap.example.com instead of performing reverse dns request and getting ticket for ldap/host.example.com. Everything works fine when I'm using ldap://host.example.com but I prefer to use service CNAME.
There is my code for creating connection factory:
public DefaultConnectionFactory connectionFactory(){
return new DefaultConnectionFactory(connectionConfig());
}
private ConnectionConfig connectionConfig(){
final SaslConfig saslConfig = new SaslConfig();
saslConfig.setMechanism(Mechanism.GSSAPI);
final BindConnectionInitializer connectionInitializer = new BindConnectionInitializer();
connectionInitializer.setBindSaslConfig(saslConfig);
ConnectionConfig connConfig = new ConnectionConfig("ldaps://ldap.example.com");
connConfig.setConnectionInitializer(connectionInitializer);
return connConfig;
}
and jaas.config:
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
keyTab="/etc/ldap.keytab"
principal="ldap#EXAMPLE.COM"
storeKey=true
useKeyTab=true
debug=true
;
};
Is there any way to change this behavior?
You should request a new certificate with ldap.example.com as the subject name and with host.example.com as a subject alternative name. The certificate negotiation is handled right before Kerberos.
A couple more suggestions:
All SPNs should be defined in your KDC:
LDAP/ldap.example.com
LDAP/host.example.com
Both of these A records should be set in DNS. Avoid use of CNAMES, while it might be OK at any given time, different browser versions and future updates could cause inconsistent behavior:
ldap.example.com
host.example.com
The principal in jaas.config and the keytab should match. You have:
principal="ldap#EXAMPLE.COM"
I suggest it should be: principal=“ldap/host.example.com“;
Finally, ldap/host.example.com should be defined as the SPN in your keytab. If it is not, it might be OK, as long as you either (1) add it as an additional SPN related in the keytab: How do you add multiple SPNs to the same keytab file for Spnego or Kerberos Configuration? or (2) see Setspn if you are using Active Directory and you application server supports it.
See further reading on GSSAPI.
Related
I created the jar from the WSDL for my client using the wsdl2java command. Now, I need to know how I can authenticate my client in order to complete an operation?
I am using CXF 2.7.16. I created my service using the generated class MyApp_Service, I am struggling with this. Isn't there a simple way to tell my client the credentials it should use to gain access to the web service?
I read about the Spring configuration, however I am unable to figure out if it applies to my case and how if yes. I tried to cast the MyApp_Service class to BindingProvider in order to use the method which consist to put the USERNAME and PASSWORD properties in the context with a value. However, MyApp_Service cannot be cast to BindingProvider.
This is my first web service client application ever. So, any help will be welcomed.
Update 2015-05-28: I tried to define the AuthenticationPolicy but is seems not working. Here is the code:
Client client = JaxWsDynamicClientFactory.newInstance().createClient(wsdlUrl);
ClientImpl clt = (ClientImpl) client;
HTTPConduit cc = (HTTPConduit) clt.getConduit();
org.apache.cxf.configuration.security.ObjectFactory secOF = new org.apache.cxf.configuration.security.ObjectFactory();
AuthorizationPolicy ap = secOF.createAuthorizationPolicy();
ap.setUserName(usagerWS);
ap.setPassword(mdpWS);
ap.setAuthorizationType("Basic");
cc.setAuthorization(ap);
Sniffing with WireShark, the Authorization header is clearly missing in the HTTP request.
What is missing?
Problem solved, here is the solution:
MyApp_Service service = new MyApp_Service(wsdlUrl, new QName(namespace, serviceName));
MyApp port = service.getMyApp();
// Set credentials
Map<String, Object> reqCtxt = ((javax.xml.ws.BindingProvider) port).getRequestContext();
reqCtxt.put(javax.xml.ws.BindingProvider.USERNAME_PROPERTY, username);
reqCtxt.put(javax.xml.ws.BindingProvider.PASSWORD_PROPERTY, password);
No more usage of the dynamic client. Only the classes generated with wsdl2java are used.
I have a server process running on a machine KERBOS.COM and its trying to connect to a LDAP Server in IN.KERBOS.COM(Sub Realm) to sync user using GSSAPI Mechanism.
By looking at GSSLOGS we can see that
the spn its trying to authenticate belong to KERBOS.COM ldap/invr28ppqa36.in.kerbos.com#KERBOS.COM
Is this any way to make it pick IN.KERBOS.COM as realm.
The default realm has to be KERBOS.COM in krb5.conf . So changing the default realm to IN.KERBOS.COM is not an option.
I am also giving it fully qualified name so there should be some way to tell it to use IN.KERBOS.COM as realm.
env.put(Context.PROVIDER_URL, String.format("ldap://%s:%d", host, port));
Subject subject = new Subject();
subject.getPrivateCredentials().add(credential);
InitialLdapContext object = Subject.doAs(subject, new PrivilegedExceptionAction<InitialLdapContext>() {
public InitialLdapContext run() throws Exception {
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
return new InitialLdapContext(env, null);
} });
LOGS
Subject.doAs fails by throwing an exception (Intercept from Logs are given)
Found ticket for **sysadmin#IN.KERBOS.COM to go to krbtgt/IN.KERBOS.COM#IN.KERBOS.COM** Credentials acquireServiceCreds: obtaining service creds for **ldap/invr28ppqa36.in.kerbos.com#KERBOS.COM**
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 17 16 23 1 3.
KrbException: Fail to create credential. (63) - No service creds
at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:301)
at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:442)
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:641)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:193)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:123)
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:232)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2740)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:316)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:193)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:211)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:154)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:84)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:307)
at javax.naming.InitialContext.init(InitialContext.java:242)
at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:153)
at (LDAPConnector.java:101)
By default a Kerberos implementation will start at the KDC of the client realm, hoping that KDC can provide a cross-realm referral to the service realm. So, you have a ticket in EXAMPLE.COM, and you're going to a host called server.subdomain.example.com. Your client is supposed to go to the EXAMPLE.COM KDC. That KDC can either say that it knows how to give you a service ticket, or can give you a special referral ticket to get closer to the service; in this example you'd kind of expect it to give you a krbtgt/SUBDOMAIN.EXAMPLE.COM#EXAMPLE.COM realm. That tells your client to go to the subdomain.example.com KDC.
Apparently, your KERBOS.COM KDC doesn't know that your LDAP server is served by the IN.KERBOS.COM realm. One way you could solve things is by fixing your KDC. That's going to be dependent on what KDC software you have. Alternatively, you can edit your krb5.conf and tell your client that everything under in.kerbos.com is served by the IN.KERBOS.COM KDC. Add a section like the following to your krb5.conf:
[domain_realm]
.in.kerbos.com = IN.KERBOS.COM
See this as an example of discussing krb5.conf including domain_realm sections in Java. See RFC 6806 for technical details of how the referrals should work.
I want to read the user's name from the Kerberos ticket in the Authorization HTTP header. I am using Java.
I have spent days trying to achieve this by reading through a bunch of sites on the subject but have not been able to do this. Kerberos is new/foreign to me.
This is what I've achieved:
When a user first accesses the site - without the Authorization header, the server responds with 401 + header: WWW-Authenticate=Negotiate.
All sorts of magical things happen on the client's side.
User returns with a HTTP request that contains the Authorization header with a value like: "Negotiate YHcGB...=="
Decode the base64 encoded ticket to a byte array.
From here on it's a terrifying journey through the unknown. As far as I can tell, the next steps should be:
Login to AD/Kerberos/ Server with a user.
Decode the ticket.
This is what I have:
login.conf
ServicePrincipalLoginContext
{
com.sun.security.auth.module.Krb5LoginModule
required
principal="HTTP/some.server.com#MY.DOMAIN.COM"
doNotPrompt=true
useTicketCache=true
password=mYpasSword
debug=true;
};
JavaClass.java
String encodedTicket = authorization.substring("Negotiate ".length());
byte[] ticket = Base64.decode(encodedTicket);
LoginContext lc = new LoginContext("ServicePrincipalLoginContext");
lc.login();
Subject serviceSubject = lc.getSubject();
Subject.doAs(serviceSubject, new ServiceTicketDecoder(ticket));
ServiceTicketDecoder.java
public String run() throws Exception {
Oid kerberos5Oid = new Oid("1.2.840.113554.1.2.2");
GSSManager gssManager = GSSManager.getInstance();
String service = "krbtgt/MY.DOMAIN.COM#MY.DOMAIN.COM";
GSSName serviceName = gssManager.createName(service, GSSName.NT_USER_NAME);
GSSCredential serviceCredentials = gssManager.createCredential(serviceName, GSSCredential.INDEFINITE_LIFETIME, kerberos5Oid, GSSCredential.ACCEPT_ONLY);
GSSContext gssContext = gssManager.createContext(serviceCredentials);
gssContext.acceptSecContext(this.serviceTicket, 0, this.serviceTicket.length);
GSSName srcName = gssContext.getSrcName();
return srcName.toString;
}
The login in JavaClass.java works ok, so I'm assuming the login.conf is ok. On "GSSCredential serviceCredentials = gssManager.createCredential(..." in the ServiceTicketDecoder.java the following exception is thrown:
java.security.PrivilegedActionException: GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos Key)
I am not sure if this is the right approach. I also don't know what the value of the "String service" should be or how to get that information. Can you help me?
EDIT:
login.conf
ServicePrincipalLoginContext
{
com.sun.security.auth.module.Krb5LoginModule
required
principal="HTTP/some.server.com#MY.DOMAIN.COM"
doNotPrompt=true
useTicketCache=true
keyTab="C:/server-http.keytab"
debug=true;
};
I have received a keytab file. Apparently the HTTP/some.server.com user's account was already a service principal account. I now have a problem on JavaClass.java at lc.login():
javax.security.auth.login.LoginException: KDC has no support for encryption type (14)
Caused by: KrbException: KDC has no support for encryption type (14)
Caused by: KrbException: Identifier doesn't match expected value (906)
The keytab file is encrypted with des-cbc-md5 and I have the following defined in the krb.conf file:
[libdefaults]
default_realm = MY.DOMAIN.COM
default_tkt_enctypes = des-cbc-md5
default_tgs_enctypes = des-cbc-md5
If I change the default enctypes to e.g. aes128-cts, I get the following exception:
javax.security.auth.login.LoginException: Do not have keys of types listed in default_tkt_enctypes available; only have keys of following type: DES CBC mode with MD5
I don't understand what is wrong...
Kerberos is a trusted third-party security system: the security token you receive from the client is decryptable only by you, and without contacting any Kerberos infrastructure servers (such as the KDC). You are on the right track; however, it appears you are missing this piece of background Kerberos knowledge to guide you in your further research.
The way this is achieved is that on the server you need a keytab file that contains your server's secret key. The Kerberos server (Microsoft Windows Server, I presume) must have a service principal account created for your service. An administrator can supply you with the keytab file generated for this account, which will contain the secret key.
You then need to configure the server to find this keytab file; it is used in the server-side step involving LoginContext.login. Your code that accepts the security context must be executed inside a doPrivileged code segment within which your server-side credentials are in effect.
If all you want is the username there is an easier way.
request.getUserPrincipal().getName()
http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()
http://docs.oracle.com/javase/7/docs/api/java/security/Principal.html
I have a simple Thrift based java application I have written. It is really very simple, not much more than a "Hello World" message transport using Thrift in java. I have been told that I need to add Kerberos support to my message. I have done some googling and am surprised that Thrift does not already have Kerberos support in some form (or if it does, I cannot find it). I thought about writing my own wrapper using GSSAPI, but I cannot wrap/unwrap my Thrift message as that screws up the Thrift message format.
Has anyone ever Kerberized Thrift?.. or know how it would be done?
Thanks, in advance.
**So, I guess there is a way to do this via the SASL/GSS APIs. It confuses me as to why I don't see any great examples of this on the internet anywhere. However, I post an example of what I have created in the hopes that it will be a help to others... or that someone can correct my delusion of doing something useful here.
Sample Server code:
TServerSocket serverTransport = new TServerSocket(7911); // new server on port 7911
HelloWorldService.Processor<Iface> processor = new HelloWorldService.Processer<Iface>(new ThriftServerImpl()); // This is my thrift implementation for my server
Map<String, String> saslProperties = new HashMap<String, String>(); // need a map for properties
saslProperties.put(Sasl.QOP, "true");
saslProperties.put(Sasl.QOP, "auth-conf"); // authorization and confidentiality
TSaslServerTransport.Factory saslTransportFactory = new TSaslServerTransport.Factory(); // Creating the server definition
saslTransportFactory.addServerDefinition(
"GSSAPI", // tell SASL to use GSSAPI, which supports Kerberos
"myserviceprincipal", // base kerberos principal name - myprincipal/my.server.com#MY.REALM
"my.server.com", // kerberos principal server - myprincipal/my.server.com#MY.REALM
saslProps, // Properties set, above
new SaslRpcServer.SaslGssCallbackHandler())); // I don't know what this really does... but I stole it from Hadoop and it works.. so there.
Tserver server = new TThreadPoolServer(newTThreadPoolSErver.Args(serverTransport).transportFactory(saslTrasnportFactory).processor(processor));
server.serve(); // Thrift server start
Sample Client Code
TTransport transport = new TSocket("my.server.com", 7911); // client to connect to server and port
saslProperties.put(Sasl.QOP, "true");
saslProperties.put(Sasl.QOP, "auth-conf"); // authorization and confidentiality
TTransport saslTransport = new TSaslTransport(
"GSSAPI", // tell SASL to use GSSAPI, which supports Kerberos
null, // authorizationid - null
"myserviceprincipal", // base kerberos principal name - myprincipal/my.client.com#MY.REALM
"my.server.com", // kerberos principal server - myprincipal/my.server.com#MY.REALM
saslProps, // Properties set, above
null, // callback handler - null
transport); // underlying transport
TProtocol protocol = new TBinaryProtocol(saslTransport); // set up our new Thrift protocol
HelloWorldService.Client client = new HelloWorldService.Client(protocol); // Setup our thrift client
saslTransport.open();
String response = client.hello("Hi There"); // send message
System.out.println("response = " + response);
transport.close();
Other condsiderations:
* I set several java properties on both the client and the server.
- java.security.krb5.realm = MY.REALM // realm name
- java.security.krb5.kdc = my.kdc.com // kdc server
- javax.security.auth.useSubjectCredsOnly = false // Allow JAAS to get the TGT.
- java.security.auth.login.config = /etc/myapp/conf/jaas.conf - required jaas file
- sun.security.krb5.debug = true // helped with diagnosing problems.
* The jaas.conf file specified, above, needs to have two entries (maybe only one per server...). I cannot remember where I gleaned this information from.. but here is my file:
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
keyTab="/etc/myapp/conf/myapp.keytab"
useTicketCache=true
principal="myuserprincipal"
debug=true;
};
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
keyTab="/etc/myapp/conf/myapp.keytab"
useTicketCache=false
principal="myserviceprincipal/my.server.com"
debug=true;
};
(back to considerations....)
* Despite having a Sasl.QOP of "auth-conf".. the first(?) message that gets transmitted is not encrypted. Maybe this is just a handshake, or something. The remaining messages appear to be encrypted, but this first one prints an ugly message to the console of "No encryption was performed by peer". It would be nice to not get that message, as it will cause grief down the road (warranted or not).
Anyway, I hope this helps someone... or alternatively can provoke some improvements that will help me. :) Hard to believe I spend 2-3 days doing this, and only a small amount of code came out of it , but I knew neither Kerberos or Thrift very well when I started.
Thanks for reading.
Edit: Appended "or LDAP" to question title to indicate that I would be fine to have a solution which made it possible for me to authenticate with LDAP credentials.
My Question: How do I authenticate a BusinessObjects session using credentials with Active Directory?
Example: I have (I think) an example from SAP on how to do this in .NET but I can't seem to find a similar solution for Java. (See this pdf and search for "Modify the .NET Web Application to enable Kerberos").
Currently: I have a solution to authenticate using an Enterprise Account:
/**
* Logs into BusinessObjects. Sets the reportEngine and biPlatform
*/
public void loginToBusinessObjects() throws AxisFault, MalformedURLException, Exception {
LogHelper.println("Server connection: " + boServer);
URL boConURL = new URL(boServer);//set connection URL
connection = new com.businessobjects.dsws.Connection(boConURL);
boSession = new Session(connection); //setup new session
EnterpriseCredential credential = EnterpriseCredential.Factory.newInstance();
credential.setLogin(boUsername);
credential.setPassword(boPassword);
LogHelper.println(boUsername + ": ##password##");
boSession.login(credential); //login to server
...
}
The code above works great.
Now: I want to be able allow users to give their Active Directory credentials and authenticate using those. I can't seem to find a way to do this however. Documentation on the code above can be found in that same pdf searching for "Logging in to a server."
Note: I could be going about this all wrong. My organization uses the same credentials for Active Directory and LDAP Authentication. If there's a way to do this using LDAP that may be sufficient. Thanks.
The answer assumes you have set up the Active Directory and/or LDAP authentication for users and the user(s) have an alias to that authentication method. This should be verifiable by logins into InfoView.
You should be able to do it by using credential.setAuthType(authType).
Where authType is
"secEnterprise" default value
"secLDAP"
"secWinAD"
Seems and makes sense that by default the AuthType is set to secEnterprise.
Note: I'm still on R3 which has a slightly different authentication mechanism and I have not specifically tried this solution.
Important Edit: The documentation (which is awful for BusinessObjects and anyone reading this probably already knows that) says that for active directory you use "secAD". However, in my testing I was able to successfully authenticate using "secWinAD" which does not appear anywhere in their documentation at all :-/ (that I could find).