----- Original Message -----
> From: [email protected]
> To: [email protected]
> Cc: [email protected]
> Sent: Monday, December 17, 2012 6:09:17 PM
> Subject: [Engine-devel] LDAP: Add support for simple authentication over SSL
>
>
> Hi,
>
> IBM Tivoli Directory Server (ITDS) supports simple authentication
> over SSL. What will it take to add this support? I can help with this
> work item but will need some guidance.
>
> Regards
> Sharad Mishra
>
Hello,
There was a discussion recently regarding this.
I paste what I wrote then...
Alon
---
Hello Thierry,
If I understand correctly you wish to help in modifying the engine to support
non GSSAPI authentication methods.
Following is a quick design goals for this implementation.
I will be glad to improve this.
Alon
---
Implementation should support the following transports:
1. LDAP (plain).
2. LDAP over TLS.
3. LDAP with StartTLS.
Implementation should support the following authentication methods:
1. Simple.
2. Digest-MD5 (plain and strong).
I believe the GSSAPI can be dropped, I see no advantage of using it.
A sample of low level implementation for transport and authentication is
attached.
When adding a domain the following facts should be provided:
1. Search user name.
2. Search user password.
3. Transport type (ldap, ldaps, ldap+startTLS)
4. Authentication (simple, Digest-MD5)
5. Sever selection policy (failover, round-robin, random).
6. Server address type (explicit, DNS record)
7. Server address set.
8. Optional base DN.
9. Optional root certificate.
10. Optional certificate chain.
11. Search page size.
10. Query timeout.
etc...
Within product there are two separate components that perform LDAP
authentication:
1. User password validation.
2. User permission fetch.
These two components needs to work in share-nothing mode, meaning that each
should communicate with directory independently with the other.
USER PASSWORD VALIDATION
Input: user
Input: domain
Input: password
Output: DN of user
Output: success/failure
Credentials used: user/password provided.
Notes: LDAP session should not be cached.
Logic: Perform LDAP bind.
USER PERMISSION FETCH
Input: DN of user (passed by user password validation)
Input: domain (passed by user password validation)
Output: A set of permissions
Credentials used: search user and password configured within system.
Notes: LDAP context can be cached.
Logic: Perform LDAP searches, this is most of current logic.
/*====================================================================
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.
====================================================================*/
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.cert.*;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;
import javax.net.*;
import javax.net.ssl.*;
public class LDAPSearch {
private boolean _doStartTLS = false;
private boolean _doSASL_digestMD5 = false;
private boolean _doSASL_strong = false;
private boolean _verifyPeerCertificate = true;
private String _securityProtocol = "TLSv1";
private String _user;
private String _password;
private int _searchPageSize;
LdapContext _ctx;
StartTlsResponse _tls;
String _nameContext = "";
public static interface ResultHandler {
public boolean handle(NamingEnumeration<SearchResult> results) throws NamingException;
}
public void useStartTLS(boolean startTLS) {
_doStartTLS = startTLS;
}
public void useDigestMD5(boolean strong) {
_doSASL_digestMD5 = true;
_doSASL_strong = strong;
}
public void setSecurityProtocol(String securityProtocol) {
_securityProtocol = securityProtocol;
}
public void setCredentials(String user, String password) {
_user = user;
_password = password;
}
public void setSearchPageSize(int searchPageSize) {
_searchPageSize = searchPageSize;
}
public void setVerifyPeerCertificate(boolean verify) {
_verifyPeerCertificate = verify;
}
public void createContext(String url)
throws NamingException, IOException {
boolean doTLS = url.startsWith("ldaps:");
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
if (doTLS) {
env.put(Context.SECURITY_PROTOCOL, _securityProtocol);
if (!_verifyPeerCertificate) {
UnsecureSSLSocketFactory.setDefaultProtocol(_securityProtocol);
env.put("java.naming.ldap.factory.socket", UnsecureSSLSocketFactory.class.getName());
}
}
_ctx = new InitialLdapContext(env, null);
if (!doTLS && _doStartTLS) {
_tls = (StartTlsResponse)_ctx.extendedOperation(
new StartTlsRequest()
);
SSLSession session = _tls.negotiate(
_verifyPeerCertificate ?
(SSLSocketFactory)SSLSocketFactory.getDefault() :
UnsecureSSLSocketFactory.createSocketFactory(_securityProtocol)
);
}
if (_user != null) {
if (_doSASL_digestMD5) {
_ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
if (_doSASL_strong) {
_ctx.addToEnvironment("javax.security.sasl.qop", "auth-conf");
_ctx.addToEnvironment("javax.security.sasl.strength", "high");
}
}
else {
_ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
}
_ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, _user);
_ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, _password);
}
/*
* no namespace, let's take the first name space
* available.
*/
if (_ctx.getNameInNamespace().equals("")) {
String contexts[] = {
"namingContexts", // standard
"defaultNamingContext" // active directory
};
Attributes attributes = _ctx.getAttributes(
"",
contexts
);
for (String context : contexts) {
Attribute attribute = attributes.get(context);
if (attribute != null) {
_nameContext = attribute.get().toString();
break;
}
}
}
}
public void destoryContext()
throws NamingException, IOException {
if (_tls != null) {
_tls.close();
}
if (_ctx != null) {
_ctx.close();
}
}
public void search(
ResultHandler handler,
String query,
String[] attributes,
int scope,
int timeLimit
) throws NamingException, IOException {
/*
* Page search.
*/
byte[] cookie = null;
boolean cont = true;
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(scope);
searchControls.setTimeLimit(timeLimit);
searchControls.setReturningAttributes(attributes);
do {
_ctx.setRequestControls(
new Control[]{
new PagedResultsControl(
_searchPageSize,
cookie,
Control.CRITICAL
)
}
);
cookie = null;
NamingEnumeration<SearchResult> results = null;
try {
results = _ctx.search(
_nameContext,
query,
searchControls
);
cont = handler.handle(results);
}
catch(PartialResultException e) {}
finally {
if (results != null) {
results.close();
results = null;
}
}
/*
* Next page
*/
if (cont && _ctx.getResponseControls() != null) {
for (Control control : _ctx.getResponseControls()) {
if (control instanceof PagedResultsResponseControl) {
cookie = ((PagedResultsResponseControl)control).getCookie();
}
}
}
} while(cont && cookie != null);
}
public static class DumpResultHandler implements ResultHandler {
private PrintStream _os;
public DumpResultHandler(OutputStream os) {
_os = new PrintStream(os);
}
public boolean handle(NamingEnumeration<SearchResult> results) throws NamingException {
/*
* Dump page results
*/
while (results != null && results.hasMore()) {
SearchResult result = results.next();
_os.println();
_os.println(result.getNameInNamespace());
NamingEnumeration<? extends Attribute> eattrs = result.getAttributes().getAll();
while(eattrs.hasMore()) {
Attribute attr = eattrs.next();
NamingEnumeration<?> eattr = attr.getAll();
while(eattr.hasMore()) {
Object o = eattr.next();
_os.println(
String.format(
"%1$s:%2$s",
attr.getID(),
o == null ? "(null)" : o.toString()
)
);
}
}
}
return true;
}
}
/*
* ldap://qa1.qa.lab.tlv.redhat.com alonbl 123456
* supports DIGEST-MD5
*
* ldap://www.trustcenter.de
* supports anonymous
* supports StartTLS
*/
public static void main(String args[]) throws Exception {
String url = args[0];
String user = null;
String password = null;
if (args.length > 1) {
user = args[1];
password = args[2];
}
String query = "(objectClass=*)";
String[] queryAttributes = { "distinguishedName", "dn", "objectClass", "cn", "mail", "name", "role", "memberOf", "group" };
LDAPSearch ldap = new LDAPSearch();
try {
/* BEGIN SETTINGS */
ldap.setVerifyPeerCertificate(false); // DEBUG ONLY!!!
ldap.useStartTLS(true);
ldap.useDigestMD5(true);
ldap.setCredentials(user, password);
ldap.setSearchPageSize(20);
/* END SETTINGS */
ldap.createContext(url);
ldap.search(
new DumpResultHandler(System.out),
query,
queryAttributes,
SearchControls.SUBTREE_SCOPE,
30000 // timeout
);
}
finally {
ldap.destoryContext();
}
}
}
import java.io.*;
import java.net.*;
import javax.net.*;
import javax.net.ssl.*;
public class UnsecureSSLSocketFactory extends SSLSocketFactory {
private static String _defaultProtocol = "SSL";
private SSLSocketFactory _next;
public static SSLSocketFactory createSocketFactory(String protocol) {
try {
SSLContext sc = SSLContext.getInstance(protocol);
sc.init(
null,
new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
},
null
);
return sc.getSocketFactory();
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void setDefaultProtocol(String protocol) {
_defaultProtocol = protocol;
}
public static SocketFactory getDefault() {
return new UnsecureSSLSocketFactory();
}
public UnsecureSSLSocketFactory() {
_next = createSocketFactory(_defaultProtocol);
}
@Override
public Socket createSocket(String host, int port)
throws IOException, UnknownHostException {
return _next.createSocket(host, port);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost,
int localPort) throws IOException, UnknownHostException {
return _next.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return _next.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress address, int port,
InetAddress localAddress, int localPort) throws IOException {
return _next.createSocket(address, port, localAddress, localPort);
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
throws IOException {
return _next.createSocket(s, host, port, autoClose);
}
@Override
public String[] getDefaultCipherSuites() {
return _next.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return _next.getSupportedCipherSuites();
}
}
_______________________________________________
Engine-devel mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-devel