KNOX-1623 - Kerberos support for KnoxShell
authorSandeep More <more@apache.org>
Wed, 5 Dec 2018 15:10:03 +0000 (10:10 -0500)
committerSandeep More <more@apache.org>
Wed, 5 Dec 2018 15:13:06 +0000 (10:13 -0500)
gateway-shell/pom.xml
gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
gateway-shell/src/main/resources/jaas.conf [new file with mode: 0644]
gateway-test-release/webhdfs-kerb-test/pom.xml
gateway-test-release/webhdfs-kerb-test/src/test/java/org/apache/knox/gateway/SecureKnoxShellTest.java [new file with mode: 0644]
gateway-test-release/webhdfs-kerb-test/src/test/resources/org/apache/knox/gateway/SecureKnoxShellTest/README [new file with mode: 0644]
gateway-test-release/webhdfs-kerb-test/src/test/resources/org/apache/knox/gateway/SecureKnoxShellTest/SecureWebHdfsPutGet.groovy [new file with mode: 0644]
pom.xml

index f73ab36..e7c0c2a 100644 (file)
             <groupId>commons-configuration</groupId>
             <artifactId>commons-configuration</artifactId>
         </dependency>
-
         <dependency>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
         </dependency>
-
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
-
+        <dependency>
+            <groupId>de.thetaphi</groupId>
+            <artifactId>forbiddenapis</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
index b9d52ca..21aa7d7 100644 (file)
@@ -29,12 +29,14 @@ public class ClientContext {
   private final PoolContext poolContext;
   private final SocketContext socketContext;
   private final ConnectionContext connectionContext;
+  private final KerberosContext kerberos;
 
   private ClientContext() {
     configuration = new MapConfiguration(new HashMap<>());
     poolContext = new PoolContext(this);
     socketContext = new SocketContext(this);
     connectionContext = new ConnectionContext(this);
+    kerberos = new KerberosContext(this);
   }
 
   private static class Context {
@@ -186,6 +188,54 @@ public class ClientContext {
     }
   }
 
+  /**
+   * Context for Kerberos properties
+   * @since 1.3.0
+   */
+  public static class KerberosContext extends Context {
+
+    private KerberosContext(ClientContext clientContext) {
+      super(clientContext, "kerberos");
+    }
+
+    public KerberosContext jaasConf(final String path) {
+      configuration.addProperty("jaasConf", path);
+      return this;
+    }
+
+    public String jaasConf() {
+      return configuration.getString("jaasConf");
+    }
+
+    public KerberosContext krb5Conf(final String path) {
+      configuration.addProperty("krb5Conf", path);
+      return this;
+    }
+
+    public String krb5Conf() {
+      return configuration.getString("krb5Conf");
+    }
+
+    public KerberosContext enable(final boolean enable) {
+      configuration.addProperty("enable", enable);
+      return this;
+    }
+
+    public boolean enable() {
+      return configuration.getBoolean("enable", false);
+    }
+
+    public KerberosContext debug(final boolean debug) {
+      configuration.addProperty("debug", debug);
+      return this;
+    }
+
+    public boolean debug() {
+      return configuration.getBoolean("debug");
+    }
+
+  }
+
   public PoolContext pool() {
     return poolContext;
   }
@@ -198,6 +248,10 @@ public class ClientContext {
     return connectionContext;
   }
 
+  public KerberosContext kerberos() {
+    return kerberos;
+  }
+
   public static ClientContext with(final String username, final String password, final String url) {
     ClientContext context = new ClientContext();
     context.configuration.addProperty("username", username);
index da57946..ca2672c 100644 (file)
  */
 package org.apache.knox.gateway.shell;
 
+import com.sun.security.auth.callback.TextCallbackHandler;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthSchemeProvider;
 import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.AuthCache;
 import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.AuthSchemes;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.config.ConnectionConfig;
 import org.apache.http.config.Registry;
@@ -35,6 +40,7 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
 import org.apache.http.conn.ssl.TrustStrategy;
 import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
 import org.apache.http.impl.client.BasicAuthCache;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -45,6 +51,9 @@ import org.apache.http.ssl.SSLContexts;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
@@ -53,10 +62,13 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URL;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivilegedAction;
 import java.security.cert.CertificateException;
 import java.util.HashMap;
 import java.util.Map;
@@ -67,6 +79,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import de.thetaphi.forbiddenapis.SuppressForbidden;
 
 public class KnoxSession implements Closeable {
 
@@ -75,6 +88,10 @@ public class KnoxSession implements Closeable {
   private static final String GATEWAY_CLIENT_TRUST = "gateway-client-trust.jks";
   private static final String KNOX_CLIENT_TRUSTSTORE_FILENAME = "KNOX_CLIENT_TRUSTSTORE_FILENAME";
   private static final String KNOX_CLIENT_TRUSTSTORE_DIR = "KNOX_CLIENT_TRUSTSTORE_DIR";
+  private static final String DEFAULT_JAAS_FILE = "/jaas.conf";
+  public static final String JGSS_LOGIN_MOUDLE = "com.sun.security.jgss.initiate";
+
+  private boolean isKerberos = false;
 
   String base;
   HttpHost host;
@@ -113,6 +130,46 @@ public class KnoxSession implements Closeable {
             .connection().secure(false).end());
   }
 
+  /**
+   * Support kerberos authentication.
+   *
+   * @param url Gateway url
+   * @param jaasConf jaas configuration (optional- can be null)
+   * @param krb5Conf kerberos configuration (optional - can be null)
+   * @param debug enable debug messages
+   * @return
+   * @throws URISyntaxException
+   * @since 1.3.0
+   */
+  public static KnoxSession kerberosLogin(final String url,
+      final String jaasConf,
+      final String krb5Conf,
+      final boolean debug)
+      throws URISyntaxException {
+
+    return new KnoxSession(ClientContext.with(url)
+        .kerberos()
+        .enable(true)
+        .jaasConf(jaasConf)
+        .krb5Conf(krb5Conf)
+        .debug(debug)
+        .end());
+  }
+
+  /**
+   * Support kerberos authentication.
+   * This method assumed kinit has already been called
+   * and the token is persisted on disk.
+   * @param url
+   * @return
+   * @throws URISyntaxException
+   * @since 1.3.0
+   */
+  public static KnoxSession kerberosLogin(final String url)
+      throws URISyntaxException {
+    return kerberosLogin(url, "", "", false);
+  }
+
   protected KnoxSession() throws KnoxShellException, URISyntaxException {
   }
 
@@ -173,24 +230,74 @@ public class KnoxSession implements Closeable {
     // Auth
     URI uri = URI.create(clientContext.url());
     host = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
-    
-    CredentialsProvider credentialsProvider = null; 
-    if (clientContext.username() != null && clientContext.password() != null) {
-      credentialsProvider = new BasicCredentialsProvider();
-      credentialsProvider.setCredentials(
-              new AuthScope(host.getHostName(), host.getPort()),
-              new UsernamePasswordCredentials(clientContext.username(), clientContext.password()));
-  
-      AuthCache authCache = new BasicAuthCache();
-      BasicScheme authScheme = new BasicScheme();
-      authCache.put(host, authScheme);
-      context = new BasicHttpContext();
-      context.setAttribute(org.apache.http.client.protocol.HttpClientContext.AUTH_CACHE, authCache);
+
+    /* kerberos auth */
+    if (clientContext.kerberos().enable()) {
+      isKerberos = true;
+      /* set up system properties */
+      if (!StringUtils.isBlank(clientContext.kerberos().krb5Conf())) {
+        System.setProperty("java.security.krb5.conf",
+            clientContext.kerberos().krb5Conf());
+      }
+
+      if (!StringUtils.isBlank(clientContext.kerberos().jaasConf())) {
+        System.setProperty("java.security.auth.login.config",
+            clientContext.kerberos().jaasConf());
+      } else {
+        final URL url = getClass().getResource(DEFAULT_JAAS_FILE);
+        System.setProperty("java.security.auth.login.config",
+            url.toExternalForm());
+      }
+
+      if (clientContext.kerberos().debug()) {
+        System.setProperty("sun.security.krb5.debug", "true");
+        System.setProperty("sun.security.jgss.debug", "true");
+      }
+
+      System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+
+      final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+
+      credentialsProvider.setCredentials(AuthScope.ANY, new Credentials() {
+        @Override
+        public Principal getUserPrincipal() {
+          return null;
+        }
+
+        @Override
+        public String getPassword() {
+          return null;
+        }
+      });
+
+      final Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
+          .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true)).build();
+
+      return HttpClients.custom()
+          .setConnectionManager(connectionManager)
+          .setDefaultAuthSchemeRegistry(authSchemeRegistry)
+          .setDefaultCredentialsProvider(credentialsProvider)
+          .build();
+
+    } else {
+      CredentialsProvider credentialsProvider = null;
+      if (clientContext.username() != null && clientContext.password() != null) {
+        credentialsProvider = new BasicCredentialsProvider();
+        credentialsProvider.setCredentials(
+            new AuthScope(host.getHostName(), host.getPort()),
+            new UsernamePasswordCredentials(clientContext.username(), clientContext.password()));
+
+        AuthCache authCache = new BasicAuthCache();
+        BasicScheme authScheme = new BasicScheme();
+        authCache.put(host, authScheme);
+        context = new BasicHttpContext();
+        context.setAttribute(org.apache.http.client.protocol.HttpClientContext.AUTH_CACHE, authCache);
+      }
+      return HttpClients.custom()
+          .setConnectionManager(connectionManager)
+          .setDefaultCredentialsProvider(credentialsProvider)
+          .build();
     }
-    return HttpClients.custom()
-        .setConnectionManager(connectionManager)
-        .setDefaultCredentialsProvider(credentialsProvider)
-        .build();
 
   }
 
@@ -276,13 +383,42 @@ public class KnoxSession implements Closeable {
     return base;
   }
 
+  @SuppressForbidden
   public CloseableHttpResponse executeNow(HttpRequest request ) throws IOException {
-    CloseableHttpResponse response = client.execute( host, request, context );
-    if( response.getStatusLine().getStatusCode() < 400 ) {
-      return response;
+    /* check for kerberos */
+    if (isKerberos) {
+      LoginContext lc;
+      try {
+        lc = new LoginContext(JGSS_LOGIN_MOUDLE, new TextCallbackHandler());
+        lc.login();
+        return Subject.doAs(lc.getSubject(),
+            (PrivilegedAction<CloseableHttpResponse>) () -> {
+              CloseableHttpResponse response = null;
+              try {
+                response = client.execute(host, request, context);
+                if (response.getStatusLine().getStatusCode() < 400) {
+                  return response;
+                } else {
+                  throw new ErrorResponse(response);
+                }
+              } catch (final IOException e) {
+                throw new KnoxShellException(e.toString(), e);
+              }
+            });
+
+      } catch (final LoginException e) {
+        throw new KnoxShellException(e.toString(), e);
+      }
+
     } else {
-      throw new ErrorResponse( response );
+      CloseableHttpResponse response = client.execute( host, request, context );
+      if( response.getStatusLine().getStatusCode() < 400 ) {
+        return response;
+      } else {
+        throw new ErrorResponse( response );
+      }
     }
+
   }
 
   public <T> Future<T> executeLater( Callable<T> callable ) {
diff --git a/gateway-shell/src/main/resources/jaas.conf b/gateway-shell/src/main/resources/jaas.conf
new file mode 100644 (file)
index 0000000..330ae7a
--- /dev/null
@@ -0,0 +1,28 @@
+//##########################################################################
+//# 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.
+//##########################################################################
+com.sun.security.jgss.initiate {
+    com.sun.security.auth.module.Krb5LoginModule required
+    useTicketCache=true
+    doNotPrompt=true
+    renewTGT=true
+    debug=true;
+    //ticketCache="/tmp/krb5cc_502"
+    //useKeyTab=true
+    //principal="guest/hostname.example.com@EXAMPLE.COM"
+    //keyTab="/Users/username/spnego.service.keytab";
+};
\ No newline at end of file
index 4ae8a03..041a685 100644 (file)
             <artifactId>httpclient</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/gateway-test-release/webhdfs-kerb-test/src/test/java/org/apache/knox/gateway/SecureKnoxShellTest.java b/gateway-test-release/webhdfs-kerb-test/src/test/java/org/apache/knox/gateway/SecureKnoxShellTest.java
new file mode 100644 (file)
index 0000000..817250e
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway;
+
+import com.mycila.xmltool.XMLDoc;
+import com.mycila.xmltool.XMLTag;
+import groovy.lang.Binding;
+import groovy.lang.GroovyShell;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.HdfsConfiguration;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.minikdc.MiniKdc;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
+import org.apache.knox.test.TestUtils;
+import org.apache.knox.test.category.ReleaseTest;
+import org.apache.log4j.PropertyConfigurator;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Locale;
+import java.util.Properties;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for KnoxShell Kerberos support
+ */
+@Category(ReleaseTest.class)
+public class SecureKnoxShellTest {
+
+  private static final String SCRIPT = "SecureWebHdfsPutGet.groovy";
+  /**
+   * Referring {@link MiniKdc} as {@link Object} to prevent the class loader
+   * from trying to load it before @BeforeClass annotation is called. Need to
+   * play this game because {@link MiniKdc} is not compatible with Java 7 so if
+   * we detect Java 7 we quit the test.
+   * <p>
+   * As result we need to up cast this object to {@link MiniKdc} every place we
+   * use it.
+   *
+   */
+  private static Object kdc;
+  private static String userName;
+  private static HdfsConfiguration configuration;
+  private static int nameNodeHttpPort;
+  private static File baseDir;
+  private static String krb5conf;
+  private static String hdfsPrincipal;
+  private static String spnegoPrincipal;
+  private static String keytab;
+  private static File ticketCache;
+
+  private static MiniDFSCluster miniDFSCluster;
+  private static GatewayTestDriver driver = new GatewayTestDriver();
+
+  /**
+   * Test should run if java major version is greater or equal to this
+   * property.
+   *
+   * @since 0.10
+   */
+  private static int JAVA_MAJOR_VERSION_FOR_TEST = 8;
+
+  public SecureKnoxShellTest() {
+    super();
+  }
+
+  @BeforeClass
+  public static void setupSuite() throws Exception {
+
+    /*
+     * Run the test only if the jre version matches the one we want, see
+     * KNOX-769
+     */
+    org.junit.Assume.assumeTrue(isJreVersionOK());
+    baseDir = new File(
+        KeyStoreTestUtil.getClasspathDir(SecureKnoxShellTest.class));
+    ticketCache = new File(
+        KeyStoreTestUtil.getClasspathDir(SecureKnoxShellTest.class)
+            + "/ticketCache");
+
+    nameNodeHttpPort = TestUtils.findFreePort();
+    configuration = new HdfsConfiguration();
+    baseDir = new File(
+        KeyStoreTestUtil.getClasspathDir(SecureKnoxShellTest.class));
+    System.setProperty(MiniDFSCluster.PROP_TEST_BUILD_DATA,
+        baseDir.getAbsolutePath());
+
+    miniDFSCluster = new MiniDFSCluster.Builder(configuration)
+        .nameNodePort(TestUtils.findFreePort())
+        .nameNodeHttpPort(nameNodeHttpPort).numDataNodes(2).format(true)
+        .racks(null).build();
+
+    initKdc();
+    setupKnox(keytab, hdfsPrincipal);
+  }
+
+  private static void initKdc() throws Exception {
+    final Properties kdcConf = MiniKdc.createConf();
+    kdc = new MiniKdc(kdcConf, baseDir);
+    ((MiniKdc) kdc).start();
+
+    userName = UserGroupInformation
+        .createUserForTesting("guest", new String[] { "users" }).getUserName();
+    final File keytabFile = new File(baseDir, userName + ".keytab");
+    keytab = keytabFile.getAbsolutePath();
+    // Windows will not reverse name lookup "127.0.0.1" to "localhost".
+    final String krbInstance = Path.WINDOWS ? "127.0.0.1" : "localhost";
+    ((MiniKdc) kdc).createPrincipal(keytabFile, userName + "/" + krbInstance,
+        "HTTP/" + krbInstance);
+
+    hdfsPrincipal =
+        userName + "/" + krbInstance + "@" + ((MiniKdc) kdc).getRealm();
+    spnegoPrincipal = "HTTP/" + krbInstance + "@" + ((MiniKdc) kdc).getRealm();
+
+    krb5conf = ((MiniKdc) kdc).getKrb5conf().getAbsolutePath();
+
+  }
+
+  @AfterClass
+  public static void cleanupSuite() throws Exception {
+    /* No need to clean up if we did not start anything */
+    if (isJreVersionOK()) {
+      ((MiniKdc) kdc).stop();
+      Files.deleteIfExists(ticketCache.toPath());
+      miniDFSCluster.shutdown();
+      driver.cleanup();
+    }
+  }
+
+  private static File setupJaasConf(File baseDir, String keyTabFile,
+      String principal) throws IOException {
+    final File file = new File(baseDir, "jaas.conf");
+    if (!file.exists()) {
+      file.createNewFile();
+    } else {
+      file.delete();
+      file.createNewFile();
+    }
+    final Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
+    String content = String.format(Locale.ROOT, "com.sun.security.jgss.initiate {\n"
+        + "com.sun.security.auth.module.Krb5LoginModule required\n"
+        + "renewTGT=true\n" + "doNotPrompt=true\n" + "useKeyTab=true\n"
+        + "keyTab=\"%s\"\n" + "principal=\"%s\"\n" + "isInitiator=true\n"
+        + "storeKey=true\n" + "useTicketCache=true\n" +
+        //"ticketCache=\"%s\"\n" +
+        "debug=false\n" + "client=true;\n" + "};\n", keyTabFile, principal);
+    writer.write(content);
+    writer.close();
+    return file;
+  }
+
+  private static void setupKnox(String keytab, String hdfsPrincipal)
+      throws Exception {
+
+    File jaasConf = setupJaasConf(baseDir, keytab, hdfsPrincipal);
+
+    System.setProperty("java.security.krb5.conf",
+        ((MiniKdc) kdc).getKrb5conf().getAbsolutePath());
+    System.setProperty("java.security.auth.login.config",
+        jaasConf.getAbsolutePath());
+    System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+    System.setProperty("sun.security.krb5.debug", "false");
+
+    System.setProperty("gateway.hadoop.kerberos.secured", "false");
+    GatewayTestConfig config = new GatewayTestConfig();
+    config.setGatewayPath("gateway");
+    config.setHadoopKerberosSecured(false);
+
+    driver.setResourceBase(SecureKnoxShellTest.class);
+    driver.setupLdap(0);
+    driver.setupGateway(config, "secure", createSecureTopology(), true);
+  }
+
+  /**
+   * Creates a Secure topology that is deployed to the gateway instance for the
+   * test suite.
+   *
+   * @return A populated XML structure for a topology file.
+   */
+  private static XMLTag createSecureTopology() {
+    XMLTag xml = XMLDoc.newDocument(true).addRoot("topology").addTag("gateway")
+        .addTag("provider").addTag("role").addText("authentication")
+        .addTag("name").addText("HadoopAuth").addTag("enabled").addText("true")
+
+        .addTag("param").addTag("name").addText("config.prefix").addTag("value")
+        .addText("hadoop.auth.config").gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.signature.secret").addTag("value")
+        .addText("knox").gotoParent()
+
+        .addTag("param").addTag("name").addText("hadoop.auth.config.type")
+        .addTag("value").addText("kerberos").gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.simple.anonymous.allowed").addTag("value")
+        .addText("false").gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.token.validity").addTag("value")
+        .addText("1800").gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.cookie.domain").addTag("value")
+        .addText("localhost").gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.cookie.path").addTag("value")
+        .addText("gateway/secure").gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.kerberos.principal").addTag("value")
+        .addText(spnegoPrincipal).gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.kerberos.keytab").addTag("value")
+        .addText(keytab).gotoParent()
+
+        .addTag("param").addTag("name")
+        .addText("hadoop.auth.config.kerberos.name.rules").addTag("value")
+        .addText("DEFAULT").gotoParent().gotoParent()
+
+        .addTag("provider").addTag("role").addText("identity-assertion")
+        .addTag("enabled").addText("true").addTag("name").addText("Default")
+        .gotoParent().addTag("provider").addTag("role").addText("authorization")
+        .addTag("enabled").addText("true").addTag("name").addText("AclsAuthz")
+        .gotoParent().gotoParent().gotoRoot()
+
+        .addTag("service").addTag("role").addText("WEBHDFS").addTag("url")
+        .addText("http://localhost:" + nameNodeHttpPort + "/webhdfs/")
+        .gotoParent().gotoRoot();
+
+    //System.out.println( "GATEWAY=" + xml.toString() );
+    return xml;
+  }
+
+  private static void setupLogging() {
+    PropertyConfigurator
+        .configure(ClassLoader.getSystemResource("log4j.properties"));
+  }
+
+  /**
+   * Check whether java version is >= {@link #JAVA_MAJOR_VERSION_FOR_TEST}
+   *
+   * @return
+   * @since 0.10
+   */
+  public static boolean isJreVersionOK() {
+
+    final String jreVersion = System.getProperty("java.version");
+    int majorVersion = Integer.parseInt(String.valueOf(jreVersion.charAt(2)));
+
+    if (majorVersion >= JAVA_MAJOR_VERSION_FOR_TEST) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Test Kerberos login using KnoxShell using keytab.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testCachedTicket() throws Exception {
+    setupLogging();
+
+    webhdfsPutGet();
+  }
+
+  /**
+   * Do the heavy lifting here.
+   *
+   * @throws Exception
+   */
+  private void webhdfsPutGet() throws Exception {
+    DistributedFileSystem fileSystem = miniDFSCluster.getFileSystem();
+    Path dir = new Path("/user/guest/example");
+    fileSystem.delete(dir, true);
+    fileSystem.mkdirs(dir, new FsPermission("777"));
+    fileSystem.setOwner(dir, "guest", "users");
+
+    final File jaasFile = setupJaasConf(baseDir, keytab, hdfsPrincipal);
+
+    final Binding binding = new Binding();
+
+    binding.setProperty("jaasConf", jaasFile.getAbsolutePath());
+    binding.setProperty("krb5conf", krb5conf);
+    binding.setProperty("gateway", driver.getClusterUrl());
+
+    URL readme = driver.getResourceUrl("README");
+    File file = new File(readme.toURI());
+    //System.out.println(file.exists());
+    binding.setProperty("file", file.getAbsolutePath());
+
+    final GroovyShell shell = new GroovyShell(binding);
+
+    shell.evaluate(getResourceUrl(SCRIPT).toURI());
+
+    String status = (String) binding.getProperty("status");
+    assertNotNull(status);
+    //System.out.println(status);
+
+    String fetchedFile = (String) binding.getProperty("fetchedFile");
+    assertNotNull(fetchedFile);
+    //`(fetchedFile);
+    assertTrue(fetchedFile.contains("README"));
+  }
+
+  public URL getResourceUrl(String resource) {
+    String filePath =
+        this.getClass().getCanonicalName().replaceAll("\\.", "/") + "/"
+            + resource;
+    URL url = ClassLoader.getSystemResource(filePath);
+    return url;
+  }
+
+}
diff --git a/gateway-test-release/webhdfs-kerb-test/src/test/resources/org/apache/knox/gateway/SecureKnoxShellTest/README b/gateway-test-release/webhdfs-kerb-test/src/test/resources/org/apache/knox/gateway/SecureKnoxShellTest/README
new file mode 100644 (file)
index 0000000..0ef01d4
--- /dev/null
@@ -0,0 +1,57 @@
+------------------------------------------------------------------------------
+README file for the Apache Knox Gateway
+------------------------------------------------------------------------------
+This distribution includes cryptographic software.  The country in 
+which you currently reside may have restrictions on the import, 
+possession, use, and/or re-export to another country, of 
+encryption software.  BEFORE using any encryption software, please 
+check your country's laws, regulations and policies concerning the
+import, possession, or use, and re-export of encryption software, to 
+see if this is permitted.  See <http://www.wassenaar.org/> for more
+information.
+
+The U.S. Government Department of Commerce, Bureau of Industry and
+Security (BIS), has classified this software as Export Commodity 
+Control Number (ECCN) 5D002.C.1, which includes information security
+software using or performing cryptographic functions with asymmetric
+algorithms.  The form and manner of this Apache Software Foundation
+distribution makes it eligible for export under the License Exception
+ENC Technology Software Unrestricted (TSU) exception (see the BIS 
+Export Administration Regulations, Section 740.13) for both object 
+code and source code.
+
+The following provides more details on the included cryptographic
+software:
+  This package includes the use of ApacheDS which is dependent upon the 
+Bouncy Castle Crypto APIs written by the Legion of the Bouncy Castle
+http://www.bouncycastle.org/ feedback-crypto@bouncycastle.org.
+
+------------------------------------------------------------------------------
+Description
+------------------------------------------------------------------------------
+Please see the Apache Knox site for detailed description.
+
+http://knox.apache.org/
+
+------------------------------------------------------------------------------
+Changes
+------------------------------------------------------------------------------
+Please see the CHANGES file.
+
+------------------------------------------------------------------------------
+Known Issues
+------------------------------------------------------------------------------
+Please see the ISSUES file.
+
+------------------------------------------------------------------------------
+Installation & Usage
+------------------------------------------------------------------------------
+Please see the Apache Knox Gateway User's Guide - available on the Knox site.
+http://knox.apache.org/
+
+------------------------------------------------------------------------------
+Troubleshooting & Filing bugs
+------------------------------------------------------------------------------
+Please see the Apache Knox Gateway User's Guide for detailed information.
+http://knox.apache.org
+
diff --git a/gateway-test-release/webhdfs-kerb-test/src/test/resources/org/apache/knox/gateway/SecureKnoxShellTest/SecureWebHdfsPutGet.groovy b/gateway-test-release/webhdfs-kerb-test/src/test/resources/org/apache/knox/gateway/SecureKnoxShellTest/SecureWebHdfsPutGet.groovy
new file mode 100644 (file)
index 0000000..1fd6906
--- /dev/null
@@ -0,0 +1,37 @@
+package org.apache.knox.gateway.ShellTest
+
+/*
+ * 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 org.apache.knox.gateway.shell.KnoxSession
+import org.apache.knox.gateway.shell.hdfs.Hdfs
+
+dataFile = "README"
+username = "guest"
+dataDir = "/user/" + username + "/example"
+
+/* Unit test will add 'gateway' and 'jaasConf' binding programmatically */
+session = KnoxSession.kerberosLogin(gateway, jaasConf, krb5conf, true)
+
+status = Hdfs.status(session).file( "/" ).now().string
+
+Hdfs.put( session ).file( file ).to( dataDir + "/" + dataFile ).now()
+Hdfs.put( session ).file( file ).to( dataDir + "/" + dataFile ).overwrite(true).permission(777).now()
+
+fetchedFile = Hdfs.get( session ).from( dataDir + "/" + dataFile).now().string
+
+session.shutdown()
diff --git a/pom.xml b/pom.xml
index de85827..20c63c4 100644 (file)
--- a/pom.xml
+++ b/pom.xml
                 <artifactId>metrics-jmx</artifactId>
                 <version>${metrics.version}</version>
             </dependency>
+            <dependency>
+                <groupId>de.thetaphi</groupId>
+                <artifactId>forbiddenapis</artifactId>
+                <version>${forbiddenapis.version}</version>
+            </dependency>
 
             <!-- ********** ********** ********** ********** ********** ********** -->
             <!-- ********** Test Dependencies                           ********** -->