Skip to content

Commit

Permalink
Merge pull request #8 from dcrissman/spring
Browse files Browse the repository at this point in the history
Extract out environment verification logic and add optional Spring Security component
  • Loading branch information
dcrissman committed Jan 4, 2018
2 parents 45f7aba + 6b10ba9 commit 259302b
Show file tree
Hide file tree
Showing 28 changed files with 949 additions and 683 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ www/*/src/main/webapp/gwt
.springBeans
*.versionsBackup
.idea/
dependency-reduced-pom.xml
145 changes: 125 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/esbtools/jboss-cert-ldap-login-module.svg?branch=master)](https://travis-ci.org/esbtools/jboss-cert-ldap-login-module.svg?branch=master)
[![Coverage Status](https://coveralls.io/repos/esbtools/jboss-cert-ldap-login-module/badge.svg?branch=master&service=github)](https://coveralls.io/github/esbtools/jboss-cert-ldap-login-module?branch=master)
[![Build Status](https://travis-ci.org/esbtools/cert-ldap-login-module.svg?branch=master)](https://travis-ci.org/esbtools/ldap-login-module.svg?branch=master)
[![Coverage Status](https://coveralls.io/repos/esbtools/cert-ldap-login-module/badge.svg?branch=master&service=github)](https://coveralls.io/github/esbtools/cert-ldap-login-module?branch=master)

# How to configure authentication/authorization on JBoss

Expand All @@ -8,24 +8,129 @@ In standalone.xml:
```
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domain name="esbtools-cert">
<authentication>
<login-module name="CertLdapLoginModule" code="org.esbtools.auth.jboss.CertLdapLoginModule" flag="required">
<module-option name="password-stacking" value="useFirstPass"/>
<module-option name="securityDomain" value="esbtools-cert"/>
<module-option name="verifier" value="org.jboss.security.auth.certs.AnyCertVerifier"/>
<module-option name="authRoleName" value="authenticated"/>
<module-option name="ldapServer" value="<ldap hostname>"/>
<module-option name="port" value="636"/>
<module-option name="searchBase" value="ou=example,dc=esbtools,dc=org"/>
<module-option name="bindDn" value="uid=esbtools-app,ou=example,dc=esbtools,dc=org"/>
<module-option name="bindPassword" value="<password>"/>
<module-option name="useSSL" value="true"/>
<module-option name="poolSize" value="5"/>
<module-option name="trustStore" value="${jboss.server.config.dir}/truststore.jks"/>
<module-option name="trustStorePassword" value="<password>"/>
</login-module>
</authentication>
<jsse keystore-password="<password>" keystore-url="file://${jboss.server.config.dir}/keystore.jks" truststore-password="<password>" truststore-url="file://${jboss.server.config.dir}/truststore.jks" client-auth="true"/>
<authentication>
<login-module name="CertLdapLoginModule" code="org.esbtools.auth.jboss.CertLdapLoginModule" flag="required">
<module-option name="password-stacking" value="useFirstPass"/>
<module-option name="securityDomain" value="esbtools-cert"/>
<module-option name="verifier" value="org.jboss.security.auth.certs.AnyCertVerifier"/>
<module-option name="authRoleName" value="authenticated"/>
<module-option name="ldapServer" value="<ldap hostname>"/>
<module-option name="port" value="636"/>
<module-option name="searchBase" value="ou=example,dc=esbtools,dc=org"/>
<module-option name="bindDn" value="uid=esbtools-app,ou=example,dc=esbtools,dc=org"/>
<module-option name="bindPassword" value="<password>"/>
<module-option name="useSSL" value="true"/>
<module-option name="poolSize" value="5"/>
<module-option name="trustStore" value="${jboss.server.config.dir}/truststore.jks"/>
<module-option name="trustStorePassword" value="<password>"/>
</login-module>
</authentication>
<jsse keystore-password="<password>" keystore-url="file://${jboss.server.config.dir}/keystore.jks" truststore-password="<password>" truststore-url="file://${jboss.server.config.dir}/truststore.jks" client-auth="true"/>
</security-domain>
</subsystem>
```

# How to configure authentication/authorization in Spring Security

Using annotation driven configuration:

``` java
import org.esbtools.auth.ldap.LdapConfiguration;
import org.esbtools.auth.spring.LdapUserDetailsService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource(value = {"classpath:/ldapconfig.properties"})
public class ApplicationConfiguration {

@Bean
public LdapConfiguration ldapConfiguration(
@Value("${ldapconfig.server}") String server,
@Value("${ldapconfig.port}") Integer port,
@Value("${ldapconfig.username}") String bindDn,
@Value("${ldapconfig.password}") String bindDNPwd,
@Value("${ldapconfig.pool_size}") Integer poolSize,
@Value("${ldapconfig.use_tls}") Boolean useSSL,
@Value("${ldapconfig.truststore}") String trustStore,
@Value("${ldapconfig.truststore_password}") String trustStorePassword,
@Value("${ldapconfig.connectionTimeoutMS}") Integer connectionTimeoutMS,
@Value("${ldapconfig.responseTimeoutMS}") Integer responseTimeoutMS,
@Value("${ldapconfig.debug}") Boolean debug,
@Value("${ldapconfig.keepAlive}") Boolean keepAlive,
@Value("${ldapconfig.poolMaxConnectionAgeMS}") Integer poolMaxConnectionAgeMS) {

LdapConfiguration config = new LdapConfiguration();
config.server(server);
config.port(port);
config.bindDn(bindDn);
config.bindDNPwd(bindDNPwd);
config.poolSize(poolSize);
config.useSSL(useSSL);
config.trustStore(trustStore);
config.trustStorePassword(trustStorePassword);
config.connectionTimeoutMS(connectionTimeoutMS);
config.responseTimeoutMS(responseTimeoutMS);
config.debug(debug);
config.keepAlive(keepAlive);
config.poolMaxConnectionAgeMS(poolMaxConnectionAgeMS);

return config;
}

@Bean
public LdapUserDetailsService ldapUserDetailsService(
LdapConfiguration ldapConfiguration,
@Value("${ldapconfig.search_base:dc=redhat,dc=com}") String searchBaseDn,
@Value("${ldapconfig.rolesCacheExpiryMS:300000}") int rolesCacheExpiryMS) throws Exception {
return new LdapUserDetailsService(
searchBaseDn,
ldapConfiguration,
rolesCacheExpiryMS);
}

}
```

``` java
import org.esbtools.auth.spring.EsbToolsExceptionTraslatingFilter;
import org.esbtools.auth.spring.EsbToolsExceptionTraslatingFilter.ErrorResponseWriter;
import org.esbtools.auth.spring.SpringCertEnvironmentVerificationFilter;
import org.esbtools.auth.spring.LdapUserDetailsService;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private LdapUserDetailsService ldapUserDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception
{
//...

http.x509()
.authenticationUserDetailsService(ldapUserDetailsService)
.and()
.addFilterAfter(
new EsbToolsExceptionTraslatingFilter(new ErrorResponseWriter() {
//...
}),
ExceptionTranslationFilter.class)
.addFilterAfter(
new SpringCertEnvironmentVerificationFilter("expectedEnvironment"),
EsbToolsExceptionTraslatingFilter.class);

//...
}

//...
}
```
33 changes: 33 additions & 0 deletions cert-ldap-login-module-common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.esbtools.auth</groupId>
<artifactId>cert-ldap-login-module</artifactId>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>cert-ldap-login-module-common</artifactId>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.esbtools.auth.servlet;

import java.io.IOException;
import java.security.cert.X509Certificate;

import javax.naming.NamingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.esbtools.auth.util.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CertEnvironmentVerificationFilter implements Filter {

private static final Logger LOGGER = LoggerFactory.getLogger(CertEnvironmentVerificationFilter.class);

private final Environment env;

public CertEnvironmentVerificationFilter(String environment) {
env = new Environment(environment);

LOGGER.info("Cert Environment: " + ((environment == null) ? "Not Set" : environment));
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
//Do Nothing!
}

@Override
public void destroy() {
//Do Nothing!
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
LOGGER.debug("Attempting Environment Cert verification");
X509Certificate certChain[] = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");

if ((null != certChain) && (certChain.length > 0)) {
LOGGER.debug("Verifying environment on cert");
String dn = certChain[0].getSubjectDN().getName();
try {
env.validate(dn);
}
catch (NamingException e) {
unsuccessfulAuthentication(request, response, e);
return; //end the chain
}
}
else {
LOGGER.debug("Cert not found. Skipping Environment Cert verification.");
}

chain.doFilter(request, response);
}

protected void unsuccessfulAuthentication(ServletRequest request,
ServletResponse response, NamingException failed) throws IOException, ServletException {
if (response instanceof HttpServletResponse) {
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}

response.setContentType("text/html");
response.getWriter().write("<html><head><title>Error</title></head><body>" + failed.getMessage() + "</body></html>");
response.getWriter().close();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.esbtools.auth.util;

import java.util.Arrays;
import java.util.List;

import javax.naming.NamingException;
import javax.naming.directory.NoSuchAttributeException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Environment {

private final Logger LOGGER = LoggerFactory.getLogger(Environment.class);

public static final String ENVIRONMENT_SEPARATOR = ",";
public static final String LOCATION = "l";
public static final String OU = "ou";

private final String environment;
private final String allAccessOu;

public String getEnvironment() {
return environment;
}

public String getAllAccessOu() {
return allAccessOu;
}

public Environment(String environment) {
this(environment, null);
}

public Environment(String environment, String allAccessOu) {
this.environment = environment;
this.allAccessOu = allAccessOu;
}

public void validate(String certificatePrincipal) throws NamingException {
if (StringUtils.isBlank(getEnvironment())) {
LOGGER.debug("No environment configured. Skipping Environment Cert verification.");
return;
}

String ou = getLDAPAttribute(certificatePrincipal, OU);
LOGGER.debug("OU from certificate: ", ou);
String location = getLDAPAttribute(certificatePrincipal, LOCATION);
LOGGER.debug("Location from certificate: ", location);

if(StringUtils.isBlank(ou)) {
throw new NoSuchAttributeException("No ou in dn, you may need to update your certificate: " + certificatePrincipal);
} else {
if(getAllAccessOu() != null && getAllAccessOu().equalsIgnoreCase(StringUtils.replace(ou, " ", ""))){
LOGGER.debug("Skipping environment validation, user ou matches {} ", getAllAccessOu());
} else {
//if dn not from allAccessOu, verify the location (l) field
//in the cert matches the configured environment
if(StringUtils.isBlank(location)) {
throw new NoSuchAttributeException("No location in dn, you may need to update your certificate: " + certificatePrincipal);
} else if(!locationMatchesEnvironment(location)){
throw new NoSuchAttributeException("Invalid location from dn, expected " + getEnvironment() + " but found l=" + location);
}
}
}
}

public String getLDAPAttribute(String certificatePrincipal, String searchAttribute) throws NamingException {
String searchName = new String();
LdapName name = new LdapName(certificatePrincipal);
for (Rdn rdn : name.getRdns()) {
if (rdn.getType().equalsIgnoreCase(searchAttribute)) {
searchName = (String) rdn.getValue();
break;
}
}
return searchName;
}

public boolean locationMatchesEnvironment(String location) {
List<String> environments;
if(getEnvironment().contains(ENVIRONMENT_SEPARATOR)) {
environments = Arrays.asList(getEnvironment().split(ENVIRONMENT_SEPARATOR));

} else {
environments = Arrays.asList(new String[] {getEnvironment()});
}
for(String environment : environments) {
if(environment.equalsIgnoreCase(location)) {
return true;
}
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.redhat.auth;
package org.esbtools.auth;

import org.esbtools.auth.util.CachedRolesProvider;
import org.esbtools.auth.util.RolesCache;
Expand Down
Loading

0 comments on commit 259302b

Please sign in to comment.