Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
#857: Tomcat security support for its conf/tomcat-users.xml. Work in …
…progress.
  • Loading branch information
davsclaus committed Dec 19, 2013
1 parent 7f187bd commit 2556c62
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 51 deletions.
84 changes: 53 additions & 31 deletions hawtio-web/src/main/java/io/hawt/system/Authenticator.java
Expand Up @@ -9,6 +9,7 @@
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
Expand All @@ -30,7 +31,7 @@ public class Authenticator {

public static void extractAuthInfo(String authHeader, ExtractAuthInfoCallback cb) {
authHeader = authHeader.trim();
String [] parts = authHeader.split(" ");
String[] parts = authHeader.split(" ");
if (parts.length != 2) {
return;
}
Expand All @@ -48,35 +49,33 @@ public static void extractAuthInfo(String authHeader, ExtractAuthInfoCallback cb
String password = parts[1];
cb.getAuthInfo(user, password);
}


}

public static AuthenticateResult authenticate(String realm, String role, String rolePrincipalClasses, HttpServletRequest request, PrivilegedCallback cb) {
public static AuthenticateResult authenticate(String realm, String role, String rolePrincipalClasses, Configuration configuration,
HttpServletRequest request, PrivilegedCallback cb) {

String authHeader = request.getHeader(HEADER_AUTHORIZATION);

if (authHeader == null || authHeader.equals("")) {
return AuthenticateResult.NO_CREDENTIALS;
}

final AuthInfo info = new AuthInfo();

Authenticator.extractAuthInfo(authHeader, new ExtractAuthInfoCallback() {
@Override
public void getAuthInfo(String userName, String password) {
info.username = userName;
info.password = password;
}
@Override
public void getAuthInfo(String userName, String password) {
info.username = userName;
info.password = password;
}
});

if (info.username == null || info.username.equals("public")) {
return AuthenticateResult.NO_CREDENTIALS;
}

if (info.set()) {

Subject subject = doAuthenticate(realm, role, rolePrincipalClasses, info.username, info.password);
Subject subject = doAuthenticate(realm, role, rolePrincipalClasses, configuration, info.username, info.password);
if (subject == null) {
return AuthenticateResult.NOT_AUTHORIZED;
}
Expand All @@ -95,32 +94,26 @@ public void getAuthInfo(String userName, String password) {
return AuthenticateResult.NO_CREDENTIALS;
}

private static Subject doAuthenticate(String realm, String role, String rolePrincipalClasses, final String username, final String password) {
private static Subject doAuthenticate(String realm, String role, String rolePrincipalClasses, Configuration configuration,
final String username, final String password) {
try {

if (LOG.isDebugEnabled()) {
LOG.debug("doAuthenticate[realm={}, role={}, rolePrincipalClasses={}, username={}, password={}]", new Object[]{realm, role, rolePrincipalClasses, username, "******"});
LOG.debug("doAuthenticate[realm={}, role={}, rolePrincipalClasses={}, configuration={}, username={}, password={}]",
new Object[]{realm, role, rolePrincipalClasses, configuration, username, "******"});
}

Subject subject = new Subject();
LoginContext loginContext = new LoginContext(realm, subject, new CallbackHandler() {
CallbackHandler handler = new AuthenticationCallbackHandler(username, password);

// call the constructor with or without the configuration as it behaves differently
LoginContext loginContext;
if (configuration != null) {
loginContext = new LoginContext(realm, subject, handler, configuration);
} else {
loginContext = new LoginContext(realm, subject, handler);
}

@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (LOG.isTraceEnabled()) {
LOG.trace("Callback type {} -> {}", callback.getClass(), callback);
}
if (callback instanceof NameCallback) {
((NameCallback)callback).setName(username);
} else if (callback instanceof PasswordCallback) {
((PasswordCallback)callback).setPassword(password.toCharArray());
} else {
LOG.warn("Unsupported callback class [" + callback.getClass().getName() + "]");
}
}
}
});
loginContext.login();

if (role != null && role.length() > 0 && rolePrincipalClasses != null && rolePrincipalClasses.length() > 0) {
Expand Down Expand Up @@ -155,9 +148,38 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback
} catch (AccountException e) {
LOG.warn("Account failure", e);
} catch (LoginException e) {
// TODO: Add some option for verbosity logging
LOG.warn("Login failed", e);
LOG.debug("Login failed", e);
}

return null;
}

private static final class AuthenticationCallbackHandler implements CallbackHandler {

private final String username;
private final String password;

private AuthenticationCallbackHandler(String username, String password) {
this.username = username;
this.password = password;
}

@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (LOG.isTraceEnabled()) {
LOG.trace("Callback type {} -> {}", callback.getClass(), callback);
}
if (callback instanceof NameCallback) {
((NameCallback) callback).setName(username);
} else if (callback instanceof PasswordCallback) {
((PasswordCallback) callback).setPassword(password.toCharArray());
} else {
LOG.warn("Unsupported callback class [" + callback.getClass().getName() + "]");
}
}
}
}
}
@@ -0,0 +1,63 @@
package io.hawt.web;

import javax.security.auth.login.Configuration;

public class AuthenticationConfiguration {

private boolean enabled;
private String realm;
private String role;
private String rolePrincipalClasses;
private Configuration configuration;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getRealm() {
return realm;
}

public void setRealm(String realm) {
this.realm = realm;
}

public String getRole() {
return role;
}

public void setRole(String role) {
this.role = role;
}

public String getRolePrincipalClasses() {
return rolePrincipalClasses;
}

public void setRolePrincipalClasses(String rolePrincipalClasses) {
this.rolePrincipalClasses = rolePrincipalClasses;
}

public Configuration getConfiguration() {
return configuration;
}

public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}

@Override
public String toString() {
return "AuthenticationConfiguration[" +
"enabled=" + enabled +
", realm='" + realm + '\'' +
", role='" + role + '\'' +
", rolePrincipalClasses='" + rolePrincipalClasses + '\'' +
", configuration=" + configuration +
']';
}
}
47 changes: 27 additions & 20 deletions hawtio-web/src/main/java/io/hawt/web/AuthenticationFilter.java
Expand Up @@ -18,6 +18,7 @@
import io.hawt.system.ConfigManager;
import io.hawt.system.Helpers;
import io.hawt.system.PrivilegedCallback;
import io.hawt.web.tomcat.TomcatLoginContextConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -34,52 +35,57 @@ public class AuthenticationFilter implements Filter {
public static final String HAWTIO_ROLE = "hawtio.role";
public static final String HAWTIO_ROLE_PRINCIPAL_CLASSES = "hawtio.rolePrincipalClasses";

private String realm;
private String role;
private boolean enabled;
private String rolePrincipalClasses;
private final AuthenticationConfiguration configuration = new AuthenticationConfiguration();

@Override
public void init(FilterConfig filterConfig) throws ServletException {

ConfigManager config = (ConfigManager) filterConfig.getServletContext().getAttribute("ConfigManager");
if (config != null) {
realm = config.get("realm", "karaf");
role = config.get("role", "admin");
rolePrincipalClasses = config.get("rolePrincipalClasses", "");
enabled = Boolean.parseBoolean(config.get("authenticationEnabled", "true"));
configuration.setRealm(config.get("realm", "karaf"));
configuration.setRole(config.get("role", "admin"));
configuration.setRolePrincipalClasses(config.get("rolePrincipalClasses", ""));
configuration.setEnabled(Boolean.parseBoolean(config.get("authenticationEnabled", "true")));
}

// JVM system properties can override always
if (System.getProperty(HAWTIO_AUTHENTICATION_ENABLED) != null) {
enabled = Boolean.getBoolean(HAWTIO_AUTHENTICATION_ENABLED);
configuration.setEnabled(Boolean.getBoolean(HAWTIO_AUTHENTICATION_ENABLED));
}
if (System.getProperty(HAWTIO_REALM) != null) {
realm = System.getProperty(HAWTIO_REALM);
configuration.setRealm(System.getProperty(HAWTIO_REALM));
}
if (System.getProperty(HAWTIO_ROLE) != null) {
role = System.getProperty(HAWTIO_ROLE);
configuration.setRole(System.getProperty(HAWTIO_ROLE));
}
if (System.getProperty(HAWTIO_ROLE_PRINCIPAL_CLASSES) != null) {
rolePrincipalClasses = System.getProperty(HAWTIO_ROLE_PRINCIPAL_CLASSES);
configuration.setRolePrincipalClasses(System.getProperty(HAWTIO_ROLE_PRINCIPAL_CLASSES));
}

// TODO: Introduce a discovery spi so we can try to figure out which runtime is in use, and auto-setup
// security accordingly, such as for Tomcat

// or infer using tomcat as realm name, or have tomcat-user-database as the realm name as convention or something
// if we use tomcat as realm then use the tomcat principal class if not set
if ("tomcat".equals(configuration.getRealm()) && "".equals(configuration.getRolePrincipalClasses())) {
configuration.setRolePrincipalClasses("io.hawt.web.tomcat.TomcatPrincipal");
configuration.setConfiguration(new TomcatLoginContextConfiguration());
}

if (LOG.isDebugEnabled()) {
LOG.debug("Initializing AuthenticationFilter [enabled:{}, realm={}, role={}, rolePrincipalClasses={}]", new Object[]{enabled, realm, role, rolePrincipalClasses});
LOG.debug("Initializing AuthenticationFilter {}", configuration);
}

if (enabled) {
LOG.info("Starting hawtio authentication filter, JAAS realm: \"" + realm + "\" authorized role: \"" + role + "\"" + " role principal classes: \"" + rolePrincipalClasses + "\"");
if (configuration.isEnabled()) {
LOG.info("Starting hawtio authentication filter, JAAS realm: \"{}\" authorized role: \"{}\" role principal classes: \"{}\"",
new Object[]{configuration.getRealm(), configuration.getRole(), configuration.getRolePrincipalClasses()});
} else {
LOG.info("Starting hawtio authentication filter, JAAS authentication disabled");
}

}

@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {

if (realm == null || realm.equals("") || !enabled) {
if (configuration.getRealm() == null || configuration.getRealm().equals("") || !configuration.isEnabled()) {
chain.doFilter(request, response);
return;
}
Expand All @@ -105,7 +111,8 @@ public void doFilter(final ServletRequest request, final ServletResponse respons

if (doAuthenticate) {
LOG.debug("Doing authentication and authorization for path {}", path);
switch (Authenticator.authenticate(realm, role, rolePrincipalClasses, httpRequest, new PrivilegedCallback() {
switch (Authenticator.authenticate(configuration.getRealm(), configuration.getRole(), configuration.getRolePrincipalClasses(),
configuration.getConfiguration(), httpRequest, new PrivilegedCallback() {
public void execute(Subject subject) throws Exception {
executeAs(request, response, chain, subject);
}
Expand Down
@@ -0,0 +1,26 @@
package io.hawt.web.tomcat;

import java.util.HashMap;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;

/**
* Configuration class to avoid having to deal with jaas.config files in the classpath
*/
public class TomcatLoginContextConfiguration extends Configuration {

private final AppConfigurationEntry entry = new TomcatAppConfigurationEntry();

@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return new AppConfigurationEntry[]{entry};
}

private static final class TomcatAppConfigurationEntry extends AppConfigurationEntry {

public TomcatAppConfigurationEntry() {
super("io.hawt.web.tomcat.TomcatUserDatabaseLoginContext", LoginModuleControlFlag.REQUIRED, new HashMap<String, Object>());
}
}

}
20 changes: 20 additions & 0 deletions hawtio-web/src/main/java/io/hawt/web/tomcat/TomcatPrincipal.java
@@ -0,0 +1,20 @@
package io.hawt.web.tomcat;

import java.io.Serializable;
import java.security.Principal;

public class TomcatPrincipal implements Principal, Serializable {

// TODO: add role

private final String name;

public TomcatPrincipal(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}
}

0 comments on commit 2556c62

Please sign in to comment.