Repository: knox Updated Branches: refs/heads/master 5e9e3cb34 -> 0baa77b86
KNOX-548: KnoxCLI adds a new system-user-auth-test command to test a topology's system username and password Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/0baa77b8 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/0baa77b8 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/0baa77b8 Branch: refs/heads/master Commit: 0baa77b86eaa813787a713e8f80c11302274f15d Parents: 5e9e3cb Author: Kevin Minder <[email protected]> Authored: Tue Jun 23 13:37:01 2015 -0400 Committer: Kevin Minder <[email protected]> Committed: Tue Jun 23 13:37:01 2015 -0400 ---------------------------------------------------------------------- CHANGES | 4 +- .../org/apache/hadoop/gateway/util/KnoxCLI.java | 377 +++++++++++++++---- .../gateway/KnoxCliLdapFuncTestNegative.java | 6 +- .../gateway/KnoxCliLdapFuncTestPositive.java | 8 +- .../hadoop/gateway/KnoxCliSysBindTest.java | 325 ++++++++++++++++ .../gateway/KnoxCliSysBindTest/users.ldif | 93 +++++ 6 files changed, 734 insertions(+), 79 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/0baa77b8/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index 961f23c..40f2757 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,10 @@ ------------------------------------------------------------------------------ -Release Notes - Apache Knox - Version 0.6.1 +Release Notes - Apache Knox - Version 0.7.0 ------------------------------------------------------------------------------ ** New Feature + * [KNOX-560] - Test LDAP Authentication+Authorization from KnoxCLI * [KNOX-547] - KnoxCLI adds new validate-topology and list-topologies commands. + * [KNOX-548] - KnoxCLI adds a new system-user-auth-test command to test a topology's system username and password ** Improvement * [KNOX-553] - Added topology validation from KnoxCLI to TopologyService deployment. http://git-wip-us.apache.org/repos/asf/knox/blob/0baa77b8/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java index 7942d68..cfed961 100644 --- a/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java +++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/util/KnoxCLI.java @@ -80,7 +80,8 @@ public class KnoxCLI extends Configured implements Tool { " [" + RedeployCommand.USAGE + "]\n" + " [" + ListTopologiesCommand.USAGE + "]\n" + " [" + ValidateTopologyCommand.USAGE + "]\n" + - " [" + LDAPAuthCommand.USAGE + "]\n"; + " [" + LDAPAuthCommand.USAGE + "]\n" + + " [" + LDAPSysBindCommand.USAGE + "]\n"; /** allows stdout to be captured if necessary */ public PrintStream out = System.out; @@ -160,7 +161,8 @@ public class KnoxCLI extends Configured implements Tool { * % knoxcli create-cert alias [--hostname h] * % knoxcli redeploy [--cluster clustername] * % knoxcli validate-topology [--cluster clustername] | [--path <path/to/file>] - * % knoxcli auth-test [--cluster clustername] [--u username] [--p password] + * % knoxcli user-auth-test [--cluster clustername] [--u username] [--p password] + * % knoxcli system-user-auth-test [--cluster clustername] [--d] * </pre> * @param args * @return @@ -204,13 +206,21 @@ public class KnoxCLI extends Configured implements Tool { printKnoxShellUsage(); return -1; } - }else if(args[i].equals("auth-test")) { + }else if(args[i].equals("user-auth-test")) { if(i + 1 >= args.length) { printKnoxShellUsage(); return -1; } else { command = new LDAPAuthCommand(); } + } else if(args[i].equals("system-user-auth-test")) { + if (i + 1 >= args.length){ + printKnoxShellUsage(); + return -1; + } else { + command = new LDAPSysBindCommand(); + } + } else if (args[i].equals("list-alias")) { command = new AliasListCommand(); } else if (args[i].equals("--value")) { @@ -843,6 +853,8 @@ public class KnoxCLI extends Configured implements Tool { public static final String DESC = "This is an internal command. It should not be used."; protected String username = null; protected char[] password = null; + protected static final String debugMessage = "For more information use --d for debug output."; + protected Topology topology; @Override public String getUsage() { @@ -851,8 +863,165 @@ public class KnoxCLI extends Configured implements Tool { @Override public void execute() { + out.println("This command does not have any functionality."); + } + + /** + * + * @param config - the path to the shiro.ini file within a topology deployment. + * @param token - token for username and password + * @return - true/false whether a user was successfully able to authenticate or not. + */ + protected boolean authenticateUser(String config, UsernamePasswordToken token){ + boolean result = false; + try { + Subject subject = getSubject(config); + try{ + subject.login(token); + if(subject.isAuthenticated()){ + result = true; + } + } catch (AuthenticationException e){ + out.println(e.getMessage()); + out.println(e.getCause().getMessage()); + if (debug) { + e.printStackTrace(out); + } else { + out.println(debugMessage); + } + } catch (NullPointerException e) { + out.println("Unable to obtain ShiroSubject"); + if (debug){ + e.printStackTrace(); + } else { + out.println(debugMessage); + } + } finally { + subject.logout(); + } + } catch ( Exception e ) { + out.println(e.getCause()); + out.println(e.getMessage()); + } + return result; + } + + /** + * + * @param userDn - fully qualified userDn used for LDAP authentication + * @return - returns the principal found in the userDn after "uid=" + */ + protected String getPrincipal(String userDn){ + String result = ""; + try { + int uidStart = userDn.indexOf("uid=") + 4; + int uidEnd = userDn.indexOf(",", uidStart); + if(uidEnd > uidStart) { + result = userDn.substring(uidStart, uidEnd); + } + } catch (NullPointerException e){ + out.println("Could not fetch principal from userDn: " + userDn); + out.println("The systemUsername should be in the same format as the main.ldapRealm.userDnTemplate"); + result = userDn; + } finally { + return result; + } + } + + /** + * + * @param t - topology configuration to use + * @param config - the path to the shiro.ini file from the topology deployment. + * @return - true/false whether LDAP successfully authenticated with system credentials. + */ + protected boolean testSysBind(Topology t, String config) { + boolean result = false; + String username = getSystemUsername(t); + char[] password = getSystemPassword(t); + + if(username == null) { + out.println("You are missing a parameter in your topology file."); + out.println("Verify that the param of name \"main.ldapRealm.contextFactory.systemUsername\" is present."); + } + + if(password == null) { + out.println("You are missing a parameter in your topology file."); + out.println("Verify that the param of name \"main.ldapRealm.contextFactory.systemPassword\" is present."); + } + + if(username != null && password != null){ + result = authenticateUser(config, new UsernamePasswordToken(username, password)); + } + return result; + } + + /** + * + * @param t - topology configuration to use + * @return - the principal of the systemUsername specified in topology. null if non-existent + */ + private String getSystemUsername(Topology t) { + final String SYSTEM_USERNAME = "main.ldapRealm.contextFactory.systemUsername"; + String user = null; + Provider shiro = t.getProvider("authentication", "ShiroProvider"); + if(shiro != null){ + Map<String, String> params = shiro.getParams(); + String userDn = params.get(SYSTEM_USERNAME); + if(userDn != null) { + user = getPrincipal(userDn); + } + } + return user; + } + + /** + * + * @param t - topology configuration to use + * @return - the systemPassword specified in topology. null if non-existent + */ + private char[] getSystemPassword(Topology t){ + final String SYSTEM_PASSWORD = "main.ldapRealm.contextFactory.systemPassword"; + String pass = null; + Provider shiro = t.getProvider("authentication", "ShiroProvider"); + if(shiro != null){ + Map<String, String> params = shiro.getParams(); + pass = params.get(SYSTEM_PASSWORD); + } + + if(pass != null) { + return pass.toCharArray(); + } else { + return null; + } + } + + + /** + * + * @param config - the shiro.ini config file created in topology deployment. + * @return returns the Subject given by the shiro config's settings. + */ + protected Subject getSubject(String config) { + try { + Factory factory = new IniSecurityManagerFactory(config); + org.apache.shiro.mgt.SecurityManager securityManager = (org.apache.shiro.mgt.SecurityManager) factory.getInstance(); + SecurityUtils.setSecurityManager(securityManager); + Subject subject = SecurityUtils.getSubject(); + if( subject != null) { + return subject; + } else { + out.println("Error Creating Subject from config at: " + config); + } + } catch (Exception e){ + out.println(e.getMessage()); + } + return null; } + /** + * prompts the user for credentials in the command line if necessary + * populates the username and password members. + */ protected void promptCredentials() { if(this.username == null){ Console c = System.console(); @@ -889,6 +1058,11 @@ public class KnoxCLI extends Configured implements Tool { } } + /** + * + * @param topologyName - the name of the topology to retrieve + * @return - Topology object with specified name. null if topology doesn't exist in TopologyService + */ protected Topology getTopology(String topologyName) { TopologyService ts = getTopologyService(); ts.reloadTopologies(); @@ -900,11 +1074,60 @@ public class KnoxCLI extends Configured implements Tool { return null; } + /** + * + * @param t - Topology to use for config + * @return - path of shiro.ini config file. + */ + protected String getConfig(Topology t){ + File tmpDir = new File(System.getProperty("java.io.tmpdir")); + DeploymentFactory.setGatewayServices(services); + WebArchive archive = DeploymentFactory.createDeployment(getGatewayConfig(), t); + File war = archive.as(ExplodedExporter.class).exportExploded(tmpDir, t.getName() + "_deploy.tmp"); + war.deleteOnExit(); + String config = war.getAbsolutePath() + "/WEB-INF/shiro.ini"; + try{ + FileUtils.forceDeleteOnExit(war); + } catch (IOException e) { + out.println(e.getMessage()); + war.deleteOnExit(); + } + return config; + } + + /** + * populates username and password if they were passed as arguments, if not will prompt user for them. + */ + void acquireCredentials(){ + if(user != null){ + this.username = user; + } + if(pass != null){ + this.password = pass.toCharArray(); + } + promptCredentials(); + } + + /** + * + * @return - true or false if the topology was acquired from the topology service and populated in the topology + * field. + */ + protected boolean acquireTopology(){ + topology = getTopology(cluster); + if(topology == null) { + out.println("ERR: Topology " + cluster + " does not exist"); + return false; + } else { + return true; + } + } + } private class LDAPAuthCommand extends LDAPCommand { - public static final String USAGE = "auth-test [--cluster clustername] [--u username] [--p password] [--g]"; + public static final String USAGE = "user-auth-test [--cluster clustername] [--u username] [--p password] [--g]"; public static final String DESC = "This command tests a cluster's configuration ability to\n " + "authenticate a user with a cluster's ShiroProvider settings.\n Use \"--g\" if you want to list the groups a" + " user is a member of. \nOptional: [--u username]: Provide a username argument to the command\n" + @@ -921,65 +1144,56 @@ public class KnoxCLI extends Configured implements Tool { @Override public void execute() { - Topology t = getTopology(cluster); - if(user != null){ - this.username = user; - } - if(pass != null){ - this.password = pass.toCharArray(); - } - if(t == null) { - out.println("ERR: Topology: " + cluster + " does not exist"); + if(!acquireTopology()){ return; } - if(t.getProvider("authentication", "ShiroProvider") == null) { - out.println("ERR: This tool currently only works with shiro as the authentication provider."); - out.println("ERR: Please update the topology to use \"ShiroProvider\" as the authentication provider."); + acquireCredentials(); + + if(topology.getProvider("authentication", "ShiroProvider") == null) { + out.println("ERR: This tool currently only works with Shiro as the authentication provider."); + out.println("Please update the topology to use \"ShiroProvider\" as the authentication provider."); return; } - promptCredentials(); if(username == null || password == null){ return; } - File tmpDir = new File(System.getProperty("java.io.tmpdir")); - DeploymentFactory.setGatewayServices(services); - WebArchive archive = DeploymentFactory.createDeployment(getGatewayConfig(), t); - File war = archive.as(ExplodedExporter.class).exportExploded(tmpDir, t.getName() + "_deploy.tmp"); - String config = war.getAbsolutePath() + "/WEB-INF/shiro.ini"; + String config = getConfig(topology); if(new File(config).exists()) { - if(authenticate(config)) { - out.println("LDAP authentication successful!"); - if( groupSet == null || groupSet.isEmpty()){ - out.println( username + " does not belong to any groups"); - if(groups){ - out.println("You were looking for this user's groups but this user does not belong to any."); - out.println("Your topology file may be incorrectly configured for group lookup."); - if(!hasGroupLookupErrors(t)){ - out.println("Some of your topology's param values may be incorrect. See the Knox Docs for help"); + if(testSysBind(topology, config)) { + if(authenticateUser(config, new UsernamePasswordToken(username, password))) { + if(groups) { + out.println("LDAP authentication successful!"); + groupSet = getGroups(topology, new UsernamePasswordToken(username, password)); + if(groupSet == null || groupSet.isEmpty()) { + out.println(username + " does not belong to any groups"); + if(groups) { + out.println("You were looking for this user's groups but this user does not belong to any."); + out.println("Your topology file may be incorrectly configured for group lookup."); + if(!hasGroupLookupErrors(topology)) { + out.println("Some of your topology's param values may be incorrect."); + out.println("Please refer to the Knox user guide to find out how to correctly configure a" + + "topology for group lookup;"); + } + } + } else if(!groupSet.isEmpty()) { + for (Object o : groupSet.toArray()) { + out.println(username + " is a member of: " + o.toString()); + } } } - } else if (!groupSet.isEmpty()) { - for (Object o : groupSet.toArray()) { - out.println(username + " is a member of: " + o.toString()); - } + } else { + out.println("ERR: Unable to authenticate user: " + username); } } else { - out.println("ERR: Unable to authenticate user: " + username); + out.println("Your topology was unable to bind to the LDAP server with system credentials."); + out.println("Please consider updating topology parameters"); } } else { out.println("ERR: No shiro config file found."); } - - //Cleanup temp dir with deployment files - try { - FileUtils.deleteDirectory(war); - } catch (IOException e) { - out.println(e.getMessage()); - out.println("ERR: Error when attempting to delete temp deployment."); - } } // returns false if any errors are printed @@ -994,7 +1208,6 @@ public class KnoxCLI extends Configured implements Tool { errs += hasParam(params, "main.ldapRealm.memberAttributeValueTemplate") ? 0 : 1; errs += hasParam(params, "main.ldapRealm.memberAttribute") ? 0 : 1; errs += hasParam(params, "main.ldapRealm.authorizationEnabled") ? 0 : 1; - errs += hasParam(params, "main.ldapRealm.authorizationEnabled") ? 0 : 1; errs += hasParam(params, "main.ldapRealm.contextFactory.systemUsername") ? 0 : 1; errs += hasParam(params, "main.ldapRealm.contextFactory.systemPassword") ? 0 : 1; errs += hasParam(params, "main.ldapRealm.userDnTemplate") ? 0 : 1; @@ -1011,39 +1224,61 @@ public class KnoxCLI extends Configured implements Tool { } else { return true; } } - protected boolean authenticate(String config) { - boolean result = false; + private HashSet<String> getGroups(Topology t, UsernamePasswordToken token){ + HashSet<String> groups = null; + Subject subject = getSubject(getConfig(t)); try { - Factory factory = new IniSecurityManagerFactory(config); - org.apache.shiro.mgt.SecurityManager securityManager = (org.apache.shiro.mgt.SecurityManager) factory.getInstance(); - SecurityUtils.setSecurityManager(securityManager); - Subject subject = SecurityUtils.getSubject(); - if (!subject.isAuthenticated()) { - UsernamePasswordToken token = new UsernamePasswordToken(username, password); - try { - subject.login(token); - result = subject.isAuthenticated(); - subject.hasRole(""); //Populate subject groups - groupSet = (HashSet) subject.getSession().getAttribute(SUBJECT_USER_GROUPS); - } catch (AuthenticationException e) { - out.println(e.getMessage()); - out.println(e.getCause().getMessage()); - if (debug) { - e.printStackTrace(out); - } else { - out.println("For more info, use --d for debug output."); - } - } finally { - subject.logout(); - } + if(!subject.isAuthenticated()) { + subject.login(token); } - }catch(Exception e){ + subject.hasRole(""); //Populate subject groups + groups = (HashSet) subject.getSession().getAttribute(SUBJECT_USER_GROUPS); + } catch (AuthenticationException e) { out.println(e.getMessage()); + if(debug){ + e.printStackTrace(); + } else { + out.println(debugMessage); + } + } catch (NullPointerException n) { + out.println(n.getMessage()); + if(debug) { + n.printStackTrace(); + } else { + out.println(debugMessage); + } } finally { - return result; + subject.logout(); + return groups; + } + } + + } + public class LDAPSysBindCommand extends LDAPCommand { + + public static final String USAGE = "system-user-auth-test [--cluster clustername] [--d]"; + public static final String DESC = "This command tests a cluster configuration's ability to\n " + + "authenticate a user with a cluster's ShiroProvider settings."; + + @Override + public String getUsage() { + return USAGE + ":\n\n" + DESC; + } + + @Override + public void execute() { + + if(!acquireTopology()){ + return; + } + if ( testSysBind(topology, getConfig(topology)) ) { + out.println("System LDAP Bind successful."); + } else { + out.println("Unable to successfully bind to LDAP server with topology credentials"); } } + } private GatewayConfig getGatewayConfig() { http://git-wip-us.apache.org/repos/asf/knox/blob/0baa77b8/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestNegative.java ---------------------------------------------------------------------- diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestNegative.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestNegative.java index ddd6c2a..60d1651 100644 --- a/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestNegative.java +++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestNegative.java @@ -276,7 +276,7 @@ public class KnoxCliLdapFuncTestNegative { KnoxCLI cli = new KnoxCLI(); cli.setConf( config ); - String args1[] = {"auth-test", "--master", "knox", "--cluster", "bad-cluster", + String args1[] = {"user-auth-test", "--master", "knox", "--cluster", "bad-cluster", "--u", username, "--p", password, "--g" }; cli.run( args1 ); @@ -291,7 +291,7 @@ public class KnoxCliLdapFuncTestNegative { cli = new KnoxCLI(); cli.setConf( config ); - String args2[] = {"auth-test", "--master", "knox", "--cluster", "bad-cluster", + String args2[] = {"user-auth-test", "--master", "knox", "--cluster", "bad-cluster", "--u", username, "--p", password, "--g" }; cli.run( args2 ); @@ -304,7 +304,7 @@ public class KnoxCliLdapFuncTestNegative { cli = new KnoxCLI(); cli.setConf( config ); - String args3[] = {"auth-test", "--master", "knox", "--cluster", "bad-cluster", + String args3[] = {"user-user-auth-test", "--master", "knox", "--cluster", "bad-cluster", "--u", username, "--p", password, "--g" }; cli.run( args3 ); http://git-wip-us.apache.org/repos/asf/knox/blob/0baa77b8/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestPositive.java ---------------------------------------------------------------------- diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestPositive.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestPositive.java index dc91ee0..72c2f60 100644 --- a/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestPositive.java +++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliLdapFuncTestPositive.java @@ -268,7 +268,7 @@ public class KnoxCliLdapFuncTestPositive { outContent.reset(); String username = "sam"; String password = "sam-password"; - String args[] = {"auth-test", "--master", "knox", "--cluster", "test-cluster", "--u", username, "--p", password, + String args[] = {"user-auth-test", "--master", "knox", "--cluster", "test-cluster", "--u", username, "--p", password, "--g"}; KnoxCLI cli = new KnoxCLI(); cli.setConf(config); @@ -283,7 +283,7 @@ public class KnoxCliLdapFuncTestPositive { cli.setConf(config); username = "bad-name"; password = "bad-password"; - String args2[] = {"auth-test", "--master", "knox", "--cluster", "test-cluster", "--u", username, "--p", password}; + String args2[] = {"user-auth-test", "--master", "knox", "--cluster", "test-cluster", "--u", username, "--p", password}; cli.run( args2 ); assertThat(outContent.toString(), containsString("LDAP authentication failed")); @@ -293,7 +293,7 @@ public class KnoxCliLdapFuncTestPositive { cli.setConf(config); username = "guest"; password = "guest-password"; - String args3[] = {"auth-test", "--master", "knox", "--cluster", "test-cluster", + String args3[] = {"user-auth-test", "--master", "knox", "--cluster", "test-cluster", "--u", username, "--p", password }; cli.run( args3 ); assertThat(outContent.toString(), containsString("LDAP authentication success")); @@ -305,7 +305,7 @@ public class KnoxCliLdapFuncTestPositive { cli.setConf(config); username = "guest"; password = "guest-password"; - String args4[] = {"auth-test", "--master", "knox", "--cluster", "cluster-dne", + String args4[] = {"user-auth-test", "--master", "knox", "--cluster", "cluster-dne", "--u", username, "--p", password }; cli.run( args4 ); assertThat(outContent.toString(), containsString("ERR: Topology")); http://git-wip-us.apache.org/repos/asf/knox/blob/0baa77b8/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliSysBindTest.java ---------------------------------------------------------------------- diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliSysBindTest.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliSysBindTest.java new file mode 100644 index 0000000..1739c32 --- /dev/null +++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/KnoxCliSysBindTest.java @@ -0,0 +1,325 @@ +/** + * 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. + */ +package org.apache.hadoop.gateway; + +import com.mycila.xmltool.XMLDoc; +import com.mycila.xmltool.XMLTag; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; +import org.apache.hadoop.gateway.security.ldap.SimpleLdapDirectoryServer; +import org.apache.hadoop.gateway.services.DefaultGatewayServices; +import org.apache.hadoop.gateway.services.ServiceLifecycleException; +import org.apache.hadoop.gateway.util.KnoxCLI; +import org.apache.log4j.Appender; +import org.hamcrest.Matchers; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; + +; + +public class KnoxCliSysBindTest { + + private static Class RESOURCE_BASE_CLASS = KnoxCliSysBindTest.class; + private static Logger LOG = LoggerFactory.getLogger( KnoxCliSysBindTest.class ); + + public static Enumeration<Appender> appenders; + public static GatewayTestConfig config; + public static GatewayServer gateway; + public static String gatewayUrl; + public static String clusterUrl; + public static SimpleLdapDirectoryServer ldap; + public static TcpTransport ldapTransport; + + private static final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private static final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private static final String uuid = UUID.randomUUID().toString(); + + @BeforeClass + public static void setupSuite() throws Exception { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + setupLdap(); + setupGateway(); + } + + @AfterClass + public static void cleanupSuite() throws Exception { + ldap.stop( true ); + + //FileUtils.deleteQuietly( new File( config.getGatewayHomeDir() ) ); + //NoOpAppender.tearDown( appenders ); + } + + public static void setupLdap( ) throws Exception { + URL usersUrl = getResourceUrl( "users.ldif" ); + int port = findFreePort(); + ldapTransport = new TcpTransport( port ); + ldap = new SimpleLdapDirectoryServer( "dc=hadoop,dc=apache,dc=org", new File( usersUrl.toURI() ), ldapTransport ); + ldap.start(); + LOG.info( "LDAP port = " + ldapTransport.getPort() ); + } + + public static void setupGateway() throws Exception { + + File targetDir = new File( System.getProperty( "user.dir" ), "target" ); + File gatewayDir = new File( targetDir, "gateway-home-" + uuid ); + gatewayDir.mkdirs(); + + GatewayTestConfig testConfig = new GatewayTestConfig(); + config = testConfig; + testConfig.setGatewayHomeDir( gatewayDir.getAbsolutePath() ); + + File topoDir = new File( testConfig.getGatewayTopologyDir() ); + topoDir.mkdirs(); + + File deployDir = new File( testConfig.getGatewayDeploymentDir() ); + deployDir.mkdirs(); + + writeTopology(topoDir, "test-cluster-1.xml", "guest", "guest-password", true); + writeTopology(topoDir, "test-cluster-2.xml", "sam", "sam-password", true); + writeTopology(topoDir, "test-cluster-3.xml", "admin", "admin-password", true); + writeTopology(topoDir, "test-cluster-4.xml", "", "", false); + + + DefaultGatewayServices srvcs = new DefaultGatewayServices(); + Map<String,String> options = new HashMap<String,String>(); + options.put( "persist-master", "false" ); + options.put( "master", "password" ); + try { + srvcs.init( testConfig, options ); + } catch ( ServiceLifecycleException e ) { + e.printStackTrace(); // I18N not required. + } + } + + private static void writeTopology(File topoDir, String name, String user, String pass, boolean goodTopology) throws Exception { + File descriptor = new File(topoDir, name); + + if(descriptor.exists()){ + descriptor.delete(); + descriptor = new File(topoDir, name); + } + + FileOutputStream stream = new FileOutputStream( descriptor, false ); + + if(goodTopology) { + createTopology(user, pass).toStream( stream ); + } else { + createBadTopology().toStream( stream ); + } + + stream.close(); + + } + + + private static int findFreePort() throws IOException { + ServerSocket socket = new ServerSocket(0); + int port = socket.getLocalPort(); + socket.close(); + return port; + } + + public static InputStream getResourceStream( String resource ) throws IOException { + return getResourceUrl( resource ).openStream(); + } + + public static URL getResourceUrl( String resource ) { + URL url = ClassLoader.getSystemResource( getResourceName( resource ) ); + assertThat( "Failed to find test resource " + resource, url, Matchers.notNullValue() ); + return url; + } + + public static String getResourceName( String resource ) { + return getResourceBaseName() + resource; + } + + public static String getResourceBaseName() { + return RESOURCE_BASE_CLASS.getName().replaceAll( "\\.", "/" ) + "/"; + } + + private static XMLTag createBadTopology(){ + XMLTag xml = XMLDoc.newDocument(true) + .addRoot("topology") + .addTag( "gateway" ) + .addTag("provider") + .addTag("role").addText("authentication") + .addTag("name").addText("ShiroProvider") + .addTag("enabled").addText("true") + .addTag( "param" ) + .addTag("name").addText("main.ldapRealm") + .addTag("value").addText("org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm").gotoParent() + .addTag( "param" ) + .addTag("name").addText("main.ldapRealm.userDnTemplate") + .addTag("value").addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent() + .addTag( "param" ) + .addTag("name").addText("main.ldapRealm.contextFactory.url") + .addTag("value").addText("ldap://localhost:" + ldapTransport.getPort()).gotoParent() + .addTag( "param" ) + .addTag("name").addText("main.ldapRealm.contextFactory.authenticationMechanism") + .addTag("value").addText("simple").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.authorizationEnabled") + .addTag("value").addText("true").gotoParent() + .addTag("param") + .addTag( "name").addText( "urls./**") + .addTag("value").addText( "authcBasic" ).gotoParent().gotoParent() + .addTag( "provider" ) + .addTag( "role" ).addText( "identity-assertion" ) + .addTag( "enabled" ).addText( "true" ) + .addTag( "name" ).addText( "Default" ).gotoParent() + .gotoRoot() + .addTag( "service") + .addTag("role").addText( "KNOX" ) + .gotoRoot(); + // System.out.println( "GATEWAY=" + xml.toString() ); + return xml; + } + + private static XMLTag createTopology(String username, String password) { + + XMLTag xml = XMLDoc.newDocument(true) + .addRoot("topology") + .addTag("gateway") + .addTag("provider") + .addTag("role").addText("authentication") + .addTag("name").addText("ShiroProvider") + .addTag("enabled").addText("true") + .addTag("param") + .addTag("name").addText("main.ldapRealm") + .addTag("value").addText("org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm").gotoParent() + .addTag("param" ) + .addTag("name").addText("main.ldapGroupContextFactory") + .addTag("value").addText("org.apache.hadoop.gateway.shirorealm.KnoxLdapContextFactory").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.searchBase") + .addTag("value").addText("ou=groups,dc=hadoop,dc=apache,dc=org").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.groupObjectClass") + .addTag("value").addText("groupOfNames").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.memberAttributeValueTemplate") + .addTag("value").addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent() + .addTag("param" ) + .addTag("name").addText("main.ldapRealm.memberAttribute") + .addTag("value").addText("member").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.authorizationEnabled") + .addTag("value").addText("true").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.contextFactory.systemUsername") + .addTag("value").addText("uid=" + username + ",ou=people,dc=hadoop,dc=apache,dc=org").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.contextFactory.systemPassword") + .addTag( "value").addText(password).gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.userDnTemplate") + .addTag("value").addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.contextFactory.url") + .addTag("value").addText("ldap://localhost:" + ldapTransport.getPort()).gotoParent() + .addTag("param") + .addTag("name").addText("main.ldapRealm.contextFactory.authenticationMechanism") + .addTag("value").addText("simple").gotoParent() + .addTag("param") + .addTag("name" ).addText("urls./**") + .addTag("value").addText("authcBasic").gotoParent().gotoParent() + .addTag("provider" ) + .addTag("role").addText( "identity-assertion" ) + .addTag( "enabled").addText( "true" ) + .addTag("name").addText( "Default" ).gotoParent() + .gotoRoot() + .addTag( "service" ) + .addTag( "role" ).addText( "test-service-role" ) + .gotoRoot(); + // System.out.println( "GATEWAY=" + xml.toString() ); + return xml; + } + + @Test + public void testLDAPAuth() throws Exception { + +// Test 1: Make sure authentication is successful + outContent.reset(); + String args[] = { "system-user-auth-test", "--master", "knox", "--cluster", "test-cluster-1", "--d" }; + KnoxCLI cli = new KnoxCLI(); + cli.setConf(config); + cli.run(args); + assertThat(outContent.toString(), containsString("System LDAP Bind successful")); + + // Test 2: Make sure authentication fails + outContent.reset(); + String args2[] = { "system-user-auth-test", "--master", "knox", "--cluster", "test-cluster-2", "--d" }; + cli = new KnoxCLI(); + cli.setConf(config); + cli.run(args2); + assertThat(outContent.toString(), containsString("System LDAP Bind successful")); + + + // Test 3: Make sure authentication is successful + outContent.reset(); + String args3[] = { "system-user-auth-test", "--master", "knox", "--cluster", "test-cluster-3", "--d" }; + cli = new KnoxCLI(); + cli.setConf(config); + cli.run(args3); + assertThat(outContent.toString(), containsString("LDAP authentication failed")); + assertThat(outContent.toString(), containsString("Unable to successfully bind to LDAP server with topology credentials")); + + // Test 4: Assert that we get a username/password not present error is printed + outContent.reset(); + String args4[] = { "system-user-auth-test", "--master", "knox", "--cluster", "test-cluster-4" }; + cli = new KnoxCLI(); + cli.setConf(config); + cli.run(args4); + assertThat(outContent.toString(), + containsString("Verify that the param of name \"main.ldapRealm.contextFactory.systemUsername\" is present.")); + assertThat(outContent.toString(), + containsString("Verify that the param of name \"main.ldapRealm.contextFactory.systemPassword\" is present.")); + + // Test 5: Assert that we get a username/password not present error is printed + outContent.reset(); + String args5[] = { "system-user-auth-test", "--master", "knox", "--cluster", "not-a-cluster" }; + cli = new KnoxCLI(); + cli.setConf(config); + cli.run(args5); + assertThat(outContent.toString(), + containsString("Topology not-a-cluster does not exist")); + + + } + + +} http://git-wip-us.apache.org/repos/asf/knox/blob/0baa77b8/gateway-test/src/test/resources/org/apache/hadoop/gateway/KnoxCliSysBindTest/users.ldif ---------------------------------------------------------------------- diff --git a/gateway-test/src/test/resources/org/apache/hadoop/gateway/KnoxCliSysBindTest/users.ldif b/gateway-test/src/test/resources/org/apache/hadoop/gateway/KnoxCliSysBindTest/users.ldif new file mode 100644 index 0000000..213be08 --- /dev/null +++ b/gateway-test/src/test/resources/org/apache/hadoop/gateway/KnoxCliSysBindTest/users.ldif @@ -0,0 +1,93 @@ +# 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. + +# this ldif file is provided as a template to illustrate +# use of ldapgroup(s) + +version: 1 + +# Please replace with site specific values +dn: dc=hadoop,dc=apache,dc=org +objectclass: organization +objectclass: dcObject +o: Hadoop +dc: hadoop + +# entry for a sample people container +# please replace with site specific values +dn: ou=people,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass:organizationalUnit +ou: people + +# entry for a sample end user +# please replace with site specific values +dn: uid=guest,ou=people,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass:person +objectclass:organizationalPerson +objectclass:inetOrgPerson +cn: Guest +sn: User +uid: guest +userPassword:guest-password + +# entry for sample user sam +dn: uid=sam,ou=people,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass:person +objectclass:organizationalPerson +objectclass:inetOrgPerson +cn: sam +sn: sam +uid: sam +userPassword:sam-password + +# entry for sample user tom +dn: uid=tom,ou=people,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass:person +objectclass:organizationalPerson +objectclass:inetOrgPerson +cn: tom +sn: tom +uid: tom +userPassword:tom-password + +# create FIRST Level groups branch +dn: ou=groups,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass:organizationalUnit +ou: groups +description: generic groups branch + +# create the analyst group under groups +dn: cn=analyst,ou=groups,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass: groupofnames +cn: analyst +description:analyst group +member: uid=sam,ou=people,dc=hadoop,dc=apache,dc=org +member: uid=tom,ou=people,dc=hadoop,dc=apache,dc=org + + +# create the scientist group under groups +dn: cn=scientist,ou=groups,dc=hadoop,dc=apache,dc=org +objectclass:top +objectclass: groupofnames +cn: scientist +description: scientist group +member: uid=sam,ou=people,dc=hadoop,dc=apache,dc=org \ No newline at end of file
