You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by pl...@apache.org on 2017/12/22 01:06:51 UTC
[1/3] directory-kerby git commit: DIRKRB-672 Embed a web server in
KDC to upgrade Kerby KDC.
Repository: directory-kerby
Updated Branches:
refs/heads/trunk c22be36bd -> 59a310120
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestUtil.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestUtil.java b/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestUtil.java
new file mode 100644
index 0000000..4e6b786
--- /dev/null
+++ b/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestUtil.java
@@ -0,0 +1,372 @@
+/**
+ * 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.kerby.has.server;
+
+import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory;
+import org.apache.hadoop.security.ssl.SSLFactory;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.server.web.WebConfigKey;
+import org.bouncycastle.x509.X509V1CertificateGenerator;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+class TestUtil {
+
+ /**
+ * system property for test data: {@value}
+ */
+ private static final String SYSPROP_TEST_DATA_DIR = "test.build.data";
+
+ /**
+ * The default path for using in Hadoop path references: {@value}
+ */
+ private static final String DEFAULT_TEST_DATA_PATH = "target/";
+
+ /**
+ * Get a temp path. This may or may not be relative; it depends on what the
+ * {@link #SYSPROP_TEST_DATA_DIR} is set to. If unset, it returns a path
+ * under the relative path {@link #DEFAULT_TEST_DATA_PATH}
+ *
+ * @param subPath sub path, with no leading "/" character
+ * @return a string to use in paths
+ */
+ public static String getTempPath(String subPath) {
+ String prop = System.getProperty(SYSPROP_TEST_DATA_DIR, DEFAULT_TEST_DATA_PATH);
+ if (prop.isEmpty()) {
+ // corner case: property is there but empty
+ prop = DEFAULT_TEST_DATA_PATH;
+ }
+ if (!prop.endsWith("/")) {
+ prop = prop + "/";
+ }
+ return prop + subPath;
+ }
+
+ public static String getClasspathDir(Class testClass) throws Exception {
+ String file = testClass.getName();
+ file = file.replace('.', '/') + ".class";
+ URL url = Thread.currentThread().getContextClassLoader().getResource(file);
+ String baseDir = url.toURI().getPath();
+ baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1);
+ return baseDir;
+ }
+
+ @SuppressWarnings("deprecation")
+ /*
+ * Create a self-signed X.509 Certificate.
+ *
+ * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
+ * @param pair the KeyPair
+ * @param days how many days from now the Certificate is valid for
+ * @param algorithm the signing algorithm, eg "SHA1withRSA"
+ * @return the self-signed certificate
+ */
+ private static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm)
+ throws CertificateEncodingException, InvalidKeyException, IllegalStateException,
+ NoSuchProviderException, NoSuchAlgorithmException, SignatureException {
+
+ Date from = new Date();
+ Date to = new Date(from.getTime() + days * 86400000L);
+ BigInteger sn = new BigInteger(64, new SecureRandom());
+ X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ X500Principal dnName = new X500Principal(dn);
+
+ certGen.setSerialNumber(sn);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(from);
+ certGen.setNotAfter(to);
+ certGen.setSubjectDN(dnName);
+ certGen.setPublicKey(pair.getPublic());
+ certGen.setSignatureAlgorithm(algorithm);
+
+ return certGen.generate(pair.getPrivate());
+ }
+
+ private static KeyPair generateKeyPair(String algorithm) throws NoSuchAlgorithmException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
+ keyGen.initialize(1024);
+ return keyGen.genKeyPair();
+ }
+
+ private static KeyStore createEmptyKeyStore() throws GeneralSecurityException, IOException {
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(null, null); // initialize
+ return ks;
+ }
+
+ private static void saveKeyStore(KeyStore ks, String filename, String password)
+ throws GeneralSecurityException, IOException {
+ FileOutputStream out = new FileOutputStream(filename);
+ ks.store(out, password.toCharArray());
+ out.close();
+ }
+
+ private static void createKeyStore(String filename, String password, String alias, Key privateKey, Certificate cert)
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = createEmptyKeyStore();
+ ks.setKeyEntry(alias, privateKey, password.toCharArray(), new Certificate[]{cert});
+ saveKeyStore(ks, filename, password);
+ }
+
+ private static <T extends Certificate> void createTrustStore(String filename, String password, Map<String, T> certs)
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = createEmptyKeyStore();
+ for (Map.Entry<String, T> cert : certs.entrySet()) {
+ ks.setCertificateEntry(cert.getKey(), cert.getValue());
+ }
+ saveKeyStore(ks, filename, password);
+ }
+
+ /**
+ * Performs complete setup of SSL configuration in preparation for testing an
+ * SSLFactory. This includes keys, certs, keystore, truststore, the server
+ * SSL configuration file, the client SSL configuration file, and the master
+ * configuration file read by the SSLFactory.
+ *
+ * @param keystoreDir String directory to save keystore
+ * @param sslConfDir String directory to save SSL configuration files
+ * @param conf Configuration master configuration to be used by an SSLFactory,
+ * which will be mutated by this method
+ * @param useClientCert boolean true to make the client present a cert in the SSL handshake
+ */
+ public static void setupSSLConfig(String keystoreDir, String sslConfDir, HasConfig conf, boolean useClientCert)
+ throws Exception {
+ setupSSLConfig(keystoreDir, sslConfDir, conf, useClientCert, true, "");
+ }
+
+ /**
+ * Performs complete setup of SSL configuration in preparation for testing an
+ * SSLFactory. This includes keys, certs, keystore, truststore, the server
+ * SSL configuration file, the client SSL configuration file, and the master
+ * configuration file read by the SSLFactory.
+ *
+ * @param keystoreDir String directory to save keystore
+ * @param sslConfDir String directory to save SSL configuration files
+ * @param conf Configuration master configuration to be used by an SSLFactory,
+ * which will be mutated by this method
+ * @param useClientCert boolean true to make the client present a cert in the SSL handshake
+ * @param trustStore boolean true to create truststore, false not to create it
+ * @param excludeCiphers String comma separated ciphers to exclude
+ * @throws Exception e
+ */
+ private static void setupSSLConfig(String keystoreDir, String sslConfDir, HasConfig conf, boolean useClientCert,
+ boolean trustStore, String excludeCiphers) throws Exception {
+ String clientKS = keystoreDir + "/clientKS.jks";
+ String clientPassword = "clientP";
+ String serverKS = keystoreDir + "/serverKS.jks";
+ String serverPassword = "serverP";
+ String trustKS = null;
+ String trustPassword = "trustP";
+
+ File sslClientConfFile = new File(sslConfDir, getClientSSLConfigFileName());
+ File sslServerConfFile = new File(sslConfDir, getServerSSLConfigFileName());
+
+ Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>();
+
+ if (useClientCert) {
+ KeyPair cKP = TestUtil.generateKeyPair("RSA");
+ X509Certificate cCert = TestUtil.generateCertificate("CN=localhost, O=client", cKP, 30, "SHA1withRSA");
+ TestUtil.createKeyStore(clientKS, clientPassword, "client", cKP.getPrivate(), cCert);
+ certs.put("client", cCert);
+ }
+
+ KeyPair sKP = TestUtil.generateKeyPair("RSA");
+ X509Certificate sCert = TestUtil.generateCertificate("CN=localhost, O=server", sKP, 30, "SHA1withRSA");
+ TestUtil.createKeyStore(serverKS, serverPassword, "server", sKP.getPrivate(), sCert);
+ certs.put("server", sCert);
+
+ if (trustStore) {
+ trustKS = keystoreDir + "/trustKS.jks";
+ TestUtil.createTrustStore(trustKS, trustPassword, certs);
+ }
+
+ HasConfig clientSSLConf = createClientSSLConfig(clientKS, clientPassword, clientPassword, trustKS, excludeCiphers);
+ HasConfig serverSSLConf = createServerSSLConfig(serverKS, serverPassword, serverPassword, trustKS, excludeCiphers);
+
+ saveConfig(sslClientConfFile, clientSSLConf);
+ saveConfig(sslServerConfFile, serverSSLConf);
+
+ conf.setString(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
+ conf.setString(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getCanonicalPath());
+ conf.setString(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getCanonicalPath());
+ conf.setString(WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY, sslServerConfFile.getAbsolutePath());
+ conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert);
+ }
+
+ /**
+ * Create SSL configuration for a client.
+ *
+ * @param clientKS String client keystore file
+ * @param password String store password, or null to avoid setting store password
+ * @param keyPassword String key password, or null to avoid setting key password
+ * @param trustKS String truststore file
+ * @param excludeCiphers String comma separated ciphers to exclude
+ * @return Configuration for client SSL
+ */
+ private static HasConfig createClientSSLConfig(String clientKS, String password, String keyPassword,
+ String trustKS, String excludeCiphers) {
+ return createSSLConfig(SSLFactory.Mode.CLIENT, clientKS, password, keyPassword, trustKS, excludeCiphers);
+ }
+
+ /**
+ * Creates SSL configuration for a server.
+ *
+ * @param serverKS String server keystore file
+ * @param password String store password, or null to avoid setting store password
+ * @param keyPassword String key password, or null to avoid setting key password
+ * @param trustKS String truststore file
+ * @param excludeCiphers String comma separated ciphers to exclude
+ * @return HasConfig
+ * @throws IOException e
+ */
+ private static HasConfig createServerSSLConfig(String serverKS, String password, String keyPassword,
+ String trustKS, String excludeCiphers) throws IOException {
+ return createSSLConfig(SSLFactory.Mode.SERVER, serverKS, password, keyPassword, trustKS, excludeCiphers);
+ }
+
+ /**
+ * Returns the client SSL configuration file name. Under parallel test
+ * execution, this file name is parametrized by a unique ID to ensure that
+ * concurrent tests don't collide on an SSL configuration file.
+ *
+ * @return client SSL configuration file name
+ */
+ private static String getClientSSLConfigFileName() {
+ return getSSLConfigFileName("ssl-client");
+ }
+
+ /**
+ * Returns the server SSL configuration file name. Under parallel test
+ * execution, this file name is parametrized by a unique ID to ensure that
+ * concurrent tests don't collide on an SSL configuration file.
+ *
+ * @return client SSL configuration file name
+ */
+ private static String getServerSSLConfigFileName() {
+ return getSSLConfigFileName("ssl-server");
+ }
+
+ /**
+ * Returns an SSL configuration file name. Under parallel test
+ * execution, this file name is parametrized by a unique ID to ensure that
+ * concurrent tests don't collide on an SSL configuration file.
+ *
+ * @param base the base of the file name
+ * @return SSL configuration file name for base
+ */
+ private static String getSSLConfigFileName(String base) {
+ String testUniqueForkId = System.getProperty("test.unique.fork.id");
+ String fileSuffix = testUniqueForkId != null ? "-" + testUniqueForkId : "";
+ return base + fileSuffix + ".xml";
+ }
+
+ /**
+ * Creates SSL configuration.
+ *
+ * @param mode SSLFactory.Mode mode to configure
+ * @param keystore String keystore file
+ * @param password String store password, or null to avoid setting store password
+ * @param keyPassword String key password, or null to avoid setting key password
+ * @param trustKS String truststore file
+ * @return Configuration for SSL
+ */
+ private static HasConfig createSSLConfig(SSLFactory.Mode mode, String keystore, String password,
+ String keyPassword, String trustKS, String excludeCiphers) {
+ String trustPassword = "trustP";
+
+ HasConfig sslConf = new HasConfig();
+ if (keystore != null) {
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore);
+ }
+ if (password != null) {
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password);
+ }
+ if (keyPassword != null) {
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY),
+ keyPassword);
+ }
+ if (trustKS != null) {
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS);
+ }
+ if (trustPassword != null) {
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY),
+ trustPassword);
+ }
+ if (null != excludeCiphers && !excludeCiphers.isEmpty()) {
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_EXCLUDE_CIPHER_LIST),
+ excludeCiphers);
+ }
+ sslConf.setString(FileBasedKeyStoresFactory.resolvePropertyName(mode,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000");
+
+ return sslConf;
+ }
+
+ /**
+ * Saves configuration to a file.
+ *
+ * @param file File to save
+ * @param conf Configuration contents to write to file
+ * @throws IOException if there is an I/O error saving the file
+ */
+ private static void saveConfig(File file, HasConfig conf) throws IOException {
+ OutputStream output = new FileOutputStream(file);
+ Properties prop = new Properties();
+
+ // set the properties value
+ for (String name : conf.getNames()) {
+ prop.setProperty(name, conf.getString(name));
+ }
+
+ // save properties to project root folder
+ prop.store(output, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestWebServer.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestWebServer.java b/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestWebServer.java
new file mode 100644
index 0000000..95a658f
--- /dev/null
+++ b/has-project/has-server/src/test/java/org/apache/kerby/has/server/TestWebServer.java
@@ -0,0 +1,126 @@
+/**
+ * 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.kerby.has.server;
+
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.http.HttpConfig.Policy;
+import org.apache.hadoop.net.NetUtils;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.util.URLConnectionFactory;
+import org.apache.kerby.has.server.web.WebConfigKey;
+import org.apache.kerby.has.server.web.WebServer;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(value = Parameterized.class)
+public class TestWebServer {
+ private static final String KEY_STORE_DIR = TestUtil.getTempPath("keystore");
+ private static File keyStoreDir = new File(KEY_STORE_DIR);
+ private static HasConfig httpsConf;
+ private static URLConnectionFactory connectionFactory;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> policy() {
+ Object[][] params = new Object[][]{{Policy.HTTP_ONLY},
+ {Policy.HTTPS_ONLY}, {Policy.HTTP_AND_HTTPS}};
+ return Arrays.asList(params);
+ }
+
+ private final Policy policy;
+
+ public TestWebServer(Policy policy) {
+ super();
+ this.policy = policy;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ httpsConf = new HasConfig();
+ // Create test keystore dir.
+ if (!keyStoreDir.exists() && !keyStoreDir.mkdirs()) {
+ System.err.println("Failed to create keystore-dir.");
+ System.exit(3);
+ }
+ String sslConfDir = TestUtil.getClasspathDir(TestWebServer.class);
+ TestUtil.setupSSLConfig(KEY_STORE_DIR, sslConfDir, httpsConf, false);
+ connectionFactory = URLConnectionFactory.newDefaultURLConnectionFactory(httpsConf);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtil.fullyDelete(keyStoreDir);
+ }
+
+ @Test
+ public void testHttpPolicy() throws Exception {
+ httpsConf.setString(WebConfigKey.HAS_HTTP_POLICY_KEY, policy.name());
+ httpsConf.setString(WebConfigKey.HAS_HTTP_ADDRESS_KEY, "localhost:11236");
+ httpsConf.setString(WebConfigKey.HAS_HTTPS_ADDRESS_KEY, "localhost:19278");
+ httpsConf.setString(WebConfigKey.HAS_AUTHENTICATION_FILTER_AUTH_TYPE, "simple");
+
+ WebServer server = null;
+ try {
+ server = new WebServer(httpsConf);
+ server.start();
+
+ Assert.assertTrue(implies(policy.isHttpEnabled(),
+ canAccess("http", server.getHttpAddress())));
+ Assert.assertTrue(implies(!policy.isHttpEnabled(),
+ server.getHttpAddress() == null));
+
+ Assert.assertTrue(implies(policy.isHttpsEnabled(),
+ canAccess("https", server.getHttpsAddress())));
+ Assert.assertTrue(implies(!policy.isHttpsEnabled(),
+ server.getHttpsAddress() == null));
+ } finally {
+ if (server != null) {
+ server.stop();
+ }
+ }
+ }
+
+ private static boolean canAccess(String scheme, InetSocketAddress address) {
+ if (address == null) {
+ return false;
+ }
+ try {
+ URL url = new URL(scheme + "://" + NetUtils.getHostPortString(address));
+ URLConnection conn = connectionFactory.openConnection(url);
+ conn.connect();
+ conn.getContent();
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean implies(boolean a, boolean b) {
+ return !a || b;
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/test/resources/webapps/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/test/resources/webapps/WEB-INF/web.xml b/has-project/has-server/src/test/resources/webapps/WEB-INF/web.xml
new file mode 100644
index 0000000..b13cb1f
--- /dev/null
+++ b/has-project/has-server/src/test/resources/webapps/WEB-INF/web.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed 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. See accompanying LICENSE file.
+-->
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
+
+</web-app>
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/test/resources/webapps/has/index.html
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/test/resources/webapps/has/index.html b/has-project/has-server/src/test/resources/webapps/has/index.html
new file mode 100644
index 0000000..6f80950
--- /dev/null
+++ b/has-project/has-server/src/test/resources/webapps/has/index.html
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="REFRESH" charset="UTF-8" />
+<title>HAS Administration</title>
+</head>
+</html>
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/pom.xml
----------------------------------------------------------------------
diff --git a/has-project/pom.xml b/has-project/pom.xml
new file mode 100644
index 0000000..4ace226
--- /dev/null
+++ b/has-project/pom.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+
+ <parent>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerby-all</artifactId>
+ <version>1.1.1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>has-project</artifactId>
+ <name>HAS project</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>has-common</module>
+ <module>has-server</module>
+ </modules>
+
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 8760fe6..6dc0c70 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,8 @@
<maven-jxr-plugin.version>2.5</maven-jxr-plugin.version>
<mockito.version>2.7.22</mockito.version>
<netty.version>4.0.52.Final</netty.version>
+ <hadoop.version>3.0.0</hadoop.version>
+ <bouncycastle.version>1.58</bouncycastle.version>
</properties>
<prerequisites>
@@ -80,6 +82,7 @@
<module>kerby-dist</module>
<module>benchmark</module>
<module>kerby-provider</module>
+ <module>has-project</module>
</modules>
<dependencyManagement>
[3/3] directory-kerby git commit: DIRKRB-672 Embed a web server in
KDC to upgrade Kerby KDC.
Posted by pl...@apache.org.
DIRKRB-672 Embed a web server in KDC to upgrade Kerby KDC.
Project: http://git-wip-us.apache.org/repos/asf/directory-kerby/repo
Commit: http://git-wip-us.apache.org/repos/asf/directory-kerby/commit/59a31012
Tree: http://git-wip-us.apache.org/repos/asf/directory-kerby/tree/59a31012
Diff: http://git-wip-us.apache.org/repos/asf/directory-kerby/diff/59a31012
Branch: refs/heads/trunk
Commit: 59a310120581a05fa3730abbbbed78f74e6a1132
Parents: c22be36
Author: plusplusjiajia <ji...@intel.com>
Authored: Fri Dec 22 09:03:12 2017 +0800
Committer: plusplusjiajia <ji...@intel.com>
Committed: Fri Dec 22 09:03:12 2017 +0800
----------------------------------------------------------------------
has-project/has-common/pom.xml | 52 ++
.../org/apache/kerby/has/common/HasConfig.java | 103 ++++
.../apache/kerby/has/common/HasConfigKey.java | 61 ++
.../apache/kerby/has/common/HasException.java | 53 ++
.../kerby/has/common/spnego/AuthToken.java | 217 +++++++
.../has/common/spnego/AuthenticatedURL.java | 282 +++++++++
.../common/spnego/AuthenticationException.java | 54 ++
.../kerby/has/common/spnego/Authenticator.java | 52 ++
.../common/spnego/KerberosAuthenticator.java | 360 +++++++++++
.../common/spnego/KerberosHasAuthenticator.java | 25 +
.../kerby/has/common/spnego/KerberosUtil.java | 262 ++++++++
.../kerby/has/common/ssl/KeyStoresFactory.java | 252 ++++++++
.../common/ssl/ReloadingX509TrustManager.java | 208 +++++++
.../apache/kerby/has/common/ssl/SSLFactory.java | 290 +++++++++
.../has/common/ssl/SSLHostnameVerifier.java | 615 +++++++++++++++++++
.../has/common/util/ConnectionConfigurator.java | 39 ++
.../apache/kerby/has/common/util/HasUtil.java | 81 +++
.../kerby/has/common/util/PlatformName.java | 59 ++
.../kerby/has/common/util/StringUtils.java | 55 ++
.../has/common/util/URLConnectionFactory.java | 197 ++++++
has-project/has-server/pom.xml | 73 +++
.../org/apache/kerby/has/server/HasServer.java | 323 ++++++++++
.../apache/kerby/has/server/web/ConfFilter.java | 55 ++
.../kerby/has/server/web/WebConfigKey.java | 62 ++
.../apache/kerby/has/server/web/WebServer.java | 335 ++++++++++
.../org/apache/kerby/has/server/TestUtil.java | 372 +++++++++++
.../apache/kerby/has/server/TestWebServer.java | 126 ++++
.../src/test/resources/webapps/WEB-INF/web.xml | 17 +
.../src/test/resources/webapps/has/index.html | 24 +
has-project/pom.xml | 23 +
pom.xml | 3 +
31 files changed, 4730 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/pom.xml
----------------------------------------------------------------------
diff --git a/has-project/has-common/pom.xml b/has-project/has-common/pom.xml
new file mode 100644
index 0000000..aaf37ce
--- /dev/null
+++ b/has-project/has-common/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+ <parent>
+ <artifactId>has-project</artifactId>
+ <groupId>org.apache.kerby</groupId>
+ <version>1.1.1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>has-common</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>token-provider</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerby-config</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-client-api-all</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-annotations</artifactId>
+ <version>${hadoop.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfig.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfig.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfig.java
new file mode 100644
index 0000000..e0f3f1e
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfig.java
@@ -0,0 +1,103 @@
+/**
+ * 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.kerby.has.common;
+
+import org.apache.kerby.kerberos.kerb.common.Krb5Conf;
+
+import java.io.File;
+
+/**
+ * AK configuration API.
+ */
+public class HasConfig extends Krb5Conf {
+ private File confDir;
+
+ public void setConfDir(File dir) {
+ this.confDir = dir;
+ }
+
+ public File getConfDir() {
+ return confDir;
+ }
+
+ public String getHttpsHost() {
+ return getString(HasConfigKey.HTTPS_HOST, false, "HAS");
+ }
+
+ public String getHttpsPort() {
+ return getString(HasConfigKey.HTTPS_PORT, false, "HAS");
+ }
+
+ public String getHttpHost() {
+ return getString(HasConfigKey.HTTP_HOST, false, "HAS");
+ }
+
+ public String getHttpPort() {
+ return getString(HasConfigKey.HTTP_PORT, false, "HAS");
+ }
+
+ public String getPluginName() {
+ return getString(HasConfigKey.AUTH_TYPE, true, "PLUGIN");
+ }
+
+ public String getRealm() {
+ return getString(HasConfigKey.REALM, false, "HAS");
+ }
+
+ public String getSslServerConf() {
+ return getString(HasConfigKey.SSL_SERVER_CONF, true, "HAS");
+ }
+
+ public String getSslClientConf() {
+ return getString(HasConfigKey.SSL_CLIENT_CONF, true, "HAS");
+ }
+
+ public String getFilterAuthType() {
+ return getString(HasConfigKey.FILTER_AUTH_TYPE, true, "HAS");
+ }
+
+ public String getKerberosPrincipal() {
+ return getString(HasConfigKey.KERBEROS_PRINCIPAL, false, "HAS");
+ }
+
+ public String getKerberosKeytab() {
+ return getString(HasConfigKey.KERBEROS_KEYTAB, false, "HAS");
+ }
+
+ public String getKerberosNameRules() {
+ return getString(HasConfigKey.KERBEROS_NAME_RULES, false, "HAS");
+ }
+
+ public String getAdminKeytab() {
+ return getString(HasConfigKey.ADMIN_KEYTAB, false, "HAS");
+ }
+
+ public String getAdminKeytabPrincipal() {
+ return getString(HasConfigKey.ADMIN_KEYTAB_PRINCIPAL, false, "HAS");
+ }
+
+ public String getEnableConf() {
+ return getString(HasConfigKey.ENABLE_CONF, false, "HAS");
+ }
+
+ public String getSslClientCert() {
+ return getString(HasConfigKey.SSL_CLIENT_CERT, true, "HAS");
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfigKey.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfigKey.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfigKey.java
new file mode 100644
index 0000000..272ab0e
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasConfigKey.java
@@ -0,0 +1,61 @@
+/**
+ * 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.kerby.has.common;
+
+import org.apache.kerby.config.ConfigKey;
+
+public enum HasConfigKey implements ConfigKey {
+ HTTP_HOST,
+ HTTP_PORT,
+ HTTPS_HOST,
+ HTTPS_PORT,
+ AUTH_TYPE("RAM"),
+ REALM,
+ ENABLE_CONF,
+ SSL_SERVER_CONF("/etc/has/ssl-server.conf"),
+ SSL_CLIENT_CONF("/etc/has/ssl-client.conf"),
+ SSL_CLIENT_CERT("/etc/has/cert-signed"),
+ FILTER_AUTH_TYPE("kerberos"),
+ KERBEROS_PRINCIPAL,
+ KERBEROS_KEYTAB,
+ KERBEROS_NAME_RULES,
+ ADMIN_KEYTAB,
+ ADMIN_KEYTAB_PRINCIPAL;
+
+ private Object defaultValue;
+
+ HasConfigKey() {
+ this.defaultValue = null;
+ }
+
+ HasConfigKey(Object defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String getPropertyKey() {
+ return name().toLowerCase();
+ }
+
+ @Override
+ public Object getDefaultValue() {
+ return this.defaultValue;
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasException.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasException.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasException.java
new file mode 100644
index 0000000..9e5db44
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/HasException.java
@@ -0,0 +1,53 @@
+/**
+ * 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.kerby.has.common;
+
+public class HasException extends Exception {
+
+ private static final long serialVersionUID = -1916788959202646914L;
+
+ /**
+ * Creates an {@link HasException}.
+ *
+ * @param cause original exception.
+ */
+ public HasException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates an {@link HasException}.
+ *
+ * @param message exception message.
+ */
+ public HasException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates an {@link HasException}.
+ *
+ * @param message exception message.
+ * @param cause original exception.
+ */
+ public HasException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthToken.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthToken.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthToken.java
new file mode 100644
index 0000000..75b405e
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthToken.java
@@ -0,0 +1,217 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.kerby.has.common.spnego;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * Borrow the class from Apache hadoop
+ */
+@SuppressWarnings("PMD")
+public class AuthToken implements Principal {
+
+ /**
+ * Constant that identifies an anonymous request.
+ */
+
+ private static final String ATTR_SEPARATOR = "&";
+ private static final String USER_NAME = "u";
+ private static final String PRINCIPAL = "p";
+ private static final String EXPIRES = "e";
+ private static final String TYPE = "t";
+
+ private static final Set<String> ATTRIBUTES =
+ new HashSet<String>(Arrays.asList(USER_NAME, PRINCIPAL, EXPIRES, TYPE));
+
+ private String userName;
+ private String principal;
+ private String type;
+ private long expires;
+ private String tokenStr;
+
+ protected AuthToken() {
+ userName = null;
+ principal = null;
+ type = null;
+ expires = -1;
+ tokenStr = "ANONYMOUS";
+ generateToken();
+ }
+
+ private static final String ILLEGAL_ARG_MSG = " is NULL, empty or contains a '" + ATTR_SEPARATOR + "'";
+
+ /**
+ * Creates an authentication token.
+ *
+ * @param userName user name.
+ * @param principal principal (commonly matches the user name, with Kerberos is the full/long principal
+ * name while the userName is the short name).
+ * @param type the authentication mechanism name.
+ * (<code>System.currentTimeMillis() + validityPeriod</code>).
+ */
+ public AuthToken(String userName, String principal, String type) {
+ checkForIllegalArgument(userName, "userName");
+ checkForIllegalArgument(principal, "principal");
+ checkForIllegalArgument(type, "type");
+ this.userName = userName;
+ this.principal = principal;
+ this.type = type;
+ this.expires = -1;
+ }
+
+ /**
+ * Check if the provided value is invalid. Throw an error if it is invalid, NOP otherwise.
+ *
+ * @param value the value to check.
+ * @param name the parameter name to use in an error message if the value is invalid.
+ */
+ protected static void checkForIllegalArgument(String value, String name) {
+ if (value == null || value.length() == 0 || value.contains(ATTR_SEPARATOR)) {
+ throw new IllegalArgumentException(name + ILLEGAL_ARG_MSG);
+ }
+ }
+
+ /**
+ * Sets the expiration of the token.
+ *
+ * @param expires expiration time of the token in milliseconds since the epoch.
+ */
+ public void setExpires(long expires) {
+ this.expires = expires;
+ generateToken();
+ }
+
+ /**
+ * Returns true if the token has expired.
+ *
+ * @return true if the token has expired.
+ */
+ public boolean isExpired() {
+ return getExpires() != -1 && System.currentTimeMillis() > getExpires();
+ }
+
+ /**
+ * Generates the token.
+ */
+ private void generateToken() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(USER_NAME).append("=").append(getUserName()).append(ATTR_SEPARATOR);
+ sb.append(PRINCIPAL).append("=").append(getName()).append(ATTR_SEPARATOR);
+ sb.append(TYPE).append("=").append(getType()).append(ATTR_SEPARATOR);
+ sb.append(EXPIRES).append("=").append(getExpires());
+ tokenStr = sb.toString();
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return the user name.
+ */
+ public String getUserName() {
+ return userName;
+ }
+
+ /**
+ * Returns the principal name (this method name comes from the JDK {@link Principal} interface).
+ *
+ * @return the principal name.
+ */
+ @Override
+ public String getName() {
+ return principal;
+ }
+
+ /**
+ * Returns the authentication mechanism of the token.
+ *
+ * @return the authentication mechanism of the token.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns the expiration time of the token.
+ *
+ * @return the expiration time of the token, in milliseconds since Epoc.
+ */
+ public long getExpires() {
+ return expires;
+ }
+
+ /**
+ * Returns the string representation of the token.
+ * <p>
+ * This string representation is parseable by the {@link #parse} method.
+ *
+ * @return the string representation of the token.
+ */
+ @Override
+ public String toString() {
+ return tokenStr;
+ }
+
+ public static AuthToken parse(String tokenStr) throws AuthenticationException {
+ if (tokenStr.length() >= 2) {
+ // strip the \" at the two ends of the tokenStr
+ if (tokenStr.charAt(0) == '\"' && tokenStr.charAt(tokenStr.length() - 1) == '\"') {
+ tokenStr = tokenStr.substring(1, tokenStr.length() - 1);
+ }
+ }
+ Map<String, String> map = split(tokenStr);
+ // remove the signature part, since client doesn't care about it
+ map.remove("s");
+
+ if (!map.keySet().equals(ATTRIBUTES)) {
+ throw new AuthenticationException("Invalid token string, missing attributes");
+ }
+ long expires = Long.parseLong(map.get(EXPIRES));
+ AuthToken token = new AuthToken(map.get(USER_NAME), map.get(PRINCIPAL), map.get(TYPE));
+ token.setExpires(expires);
+ return token;
+ }
+
+ /**
+ * Splits the string representation of a token into attributes pairs.
+ *
+ * @param tokenStr string representation of a token.
+ *
+ * @return a map with the attribute pairs of the token.
+ *
+ * @throws AuthenticationException thrown if the string representation of the token could not be broken into
+ * attribute pairs.
+ */
+ private static Map<String, String> split(String tokenStr) throws AuthenticationException {
+ Map<String, String> map = new HashMap<String, String>();
+ StringTokenizer st = new StringTokenizer(tokenStr, ATTR_SEPARATOR);
+ while (st.hasMoreTokens()) {
+ String part = st.nextToken();
+ int separator = part.indexOf('=');
+ if (separator == -1) {
+ throw new AuthenticationException("Invalid authentication token");
+ }
+ String key = part.substring(0, separator);
+ String value = part.substring(separator + 1);
+ map.put(key, value);
+ }
+ return map;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticatedURL.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticatedURL.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticatedURL.java
new file mode 100644
index 0000000..372c5cd
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticatedURL.java
@@ -0,0 +1,282 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.kerby.has.common.spnego;
+
+import org.apache.kerby.has.common.util.ConnectionConfigurator;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * <p>
+ * The authentication mechanisms supported by default are Hadoop Simple authentication
+ * (also known as pseudo authentication) and Kerberos SPNEGO authentication.
+ * <p>
+ * Additional authentication mechanisms can be supported via {@link Authenticator} implementations.
+ * <p>
+ * The default {@link Authenticator} is the {@link KerberosAuthenticator} class which supports
+ * automatic fallback from Kerberos SPNEGO to Hadoop Simple authentication.
+ * <p>
+ * <code>AuthenticatedURL</code> instances are not thread-safe.
+ * <p>
+ * The usage pattern of the {@link AuthenticatedURL} is:
+ * <pre>
+ *
+ * // establishing an initial connection
+ *
+ * URL url = new URL("http://foo:8080/bar");
+ * AuthenticatedURL.Token token = new AuthenticatedURL.Token();
+ * AuthenticatedURL aUrl = new AuthenticatedURL();
+ * HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection();
+ * ....
+ * // use the 'conn' instance
+ * ....
+ *
+ * // establishing a follow up connection using a token from the previous connection
+ *
+ * HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection();
+ * ....
+ * // use the 'conn' instance
+ * ....
+ *
+ * </pre>
+ */
+public class AuthenticatedURL {
+
+ /**
+ * Name of the HTTP cookie used for the authentication token between the client and the server.
+ */
+ public static final String AUTH_COOKIE = "hadoop.auth";
+
+ private static final String AUTH_COOKIE_EQ = AUTH_COOKIE + "=";
+
+ /**
+ * Client side authentication token.
+ */
+ public static class Token {
+
+ private String token;
+
+ /**
+ * Creates a token.
+ */
+ public Token() {
+ }
+
+ /**
+ * Creates a token using an existing string representation of the token.
+ *
+ * @param tokenStr string representation of the tokenStr.
+ */
+ public Token(String tokenStr) {
+ if (tokenStr == null) {
+ throw new IllegalArgumentException("tokenStr cannot be null");
+ }
+ set(tokenStr);
+ }
+
+ /**
+ * Returns if a token from the server has been set.
+ *
+ * @return if a token from the server has been set.
+ */
+ public boolean isSet() {
+ return token != null;
+ }
+
+ /**
+ * Sets a token.
+ *
+ * @param tokenStr string representation of the tokenStr.
+ */
+ void set(String tokenStr) {
+ token = tokenStr;
+ }
+
+ /**
+ * Returns the string representation of the token.
+ *
+ * @return the string representation of the token.
+ */
+ @Override
+ public String toString() {
+ return token;
+ }
+
+ }
+
+ private static Class<? extends Authenticator> defaultAuthenticator
+ = KerberosAuthenticator.class;
+
+ /**
+ * Sets the default {@link Authenticator} class to use when an {@link AuthenticatedURL} instance
+ * is created without specifying an authenticator.
+ *
+ * @param authenticator the authenticator class to use as default.
+ */
+ public static void setDefaultAuthenticator(Class<? extends Authenticator> authenticator) {
+ defaultAuthenticator = authenticator;
+ }
+
+ /**
+ * Returns the default {@link Authenticator} class to use when an {@link AuthenticatedURL} instance
+ * is created without specifying an authenticator.
+ *
+ * @return the authenticator class to use as default.
+ */
+ public static Class<? extends Authenticator> getDefaultAuthenticator() {
+ return defaultAuthenticator;
+ }
+
+ private Authenticator authenticator;
+ private ConnectionConfigurator connConfigurator;
+
+ /**
+ * Creates an {@link AuthenticatedURL}.
+ */
+ public AuthenticatedURL() {
+ this(null);
+ }
+
+ /**
+ * Creates an <code>AuthenticatedURL</code>.
+ *
+ * @param authenticator the {@link Authenticator} instance to use, if <code>null</code> a {@link
+ * KerberosAuthenticator} is used.
+ */
+ public AuthenticatedURL(Authenticator authenticator) {
+ this(authenticator, null);
+ }
+
+ /**
+ * Creates an <code>AuthenticatedURL</code>.
+ *
+ * @param authenticator the {@link Authenticator} instance to use, if <code>null</code> a {@link
+ * KerberosAuthenticator} is used.
+ * @param connConfigurator a connection configurator.
+ */
+ public AuthenticatedURL(Authenticator authenticator,
+ ConnectionConfigurator connConfigurator) {
+ try {
+ this.authenticator = (authenticator != null) ? authenticator : defaultAuthenticator.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ this.connConfigurator = connConfigurator;
+ this.authenticator.setConnectionConfigurator(connConfigurator);
+ }
+
+ /**
+ * Returns the {@link Authenticator} instance used by the
+ * <code>AuthenticatedURL</code>.
+ *
+ * @return the {@link Authenticator} instance
+ */
+ protected Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+ /**
+ * Returns an authenticated {@link HttpURLConnection}.
+ *
+ * @param url the URL to connect to. Only HTTP/S URLs are supported.
+ * @param token the authentication token being used for the user.
+ *
+ * @return an authenticated {@link HttpURLConnection}.
+ *
+ * @throws IOException if an IO error occurred.
+ * @throws AuthenticationException if an authentication exception occurred.
+ */
+ public HttpURLConnection openConnection(URL url, Token token) throws IOException, AuthenticationException {
+ if (url == null) {
+ throw new IllegalArgumentException("url cannot be NULL");
+ }
+ if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
+ throw new IllegalArgumentException("url must be for a HTTP or HTTPS resource");
+ }
+ if (token == null) {
+ throw new IllegalArgumentException("token cannot be NULL");
+ }
+ authenticator.authenticate(url, token);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ if (connConfigurator != null) {
+ conn = connConfigurator.configure(conn);
+ }
+ injectToken(conn, token);
+ return conn;
+ }
+
+ /**
+ * Helper method that injects an authentication token to send with a connection.
+ *
+ * @param conn connection to inject the authentication token into.
+ * @param token authentication token to inject.
+ */
+ public static void injectToken(HttpURLConnection conn, Token token) {
+ String t = token.token;
+ if (t != null) {
+ if (!t.startsWith("\"")) {
+ t = "\"" + t + "\"";
+ }
+ conn.addRequestProperty("Cookie", AUTH_COOKIE_EQ + t);
+ }
+ }
+
+ /**
+ * Helper method that extracts an authentication token received from a connection.
+ * <p>
+ * This method is used by {@link Authenticator} implementations.
+ *
+ * @param conn connection to extract the authentication token from.
+ * @param token the authentication token.
+ *
+ * @throws IOException if an IO error occurred.
+ * @throws AuthenticationException if an authentication exception occurred.
+ */
+ public static void extractToken(HttpURLConnection conn, Token token) throws IOException, AuthenticationException {
+ int respCode = conn.getResponseCode();
+ if (respCode == HttpURLConnection.HTTP_OK
+ || respCode == HttpURLConnection.HTTP_CREATED
+ || respCode == HttpURLConnection.HTTP_ACCEPTED) {
+ Map<String, List<String>> headers = conn.getHeaderFields();
+ List<String> cookies = headers.get("Set-Cookie");
+ if (cookies != null) {
+ for (String cookie : cookies) {
+ if (cookie.startsWith(AUTH_COOKIE_EQ)) {
+ String value = cookie.substring(AUTH_COOKIE_EQ.length());
+ int separator = value.indexOf(";");
+ if (separator > -1) {
+ value = value.substring(0, separator);
+ }
+ if (value.length() > 0) {
+ token.set(value);
+ }
+ }
+ }
+ }
+ } else {
+ token.set(null);
+ throw new AuthenticationException("Authentication failed, status: " + conn.getResponseCode()
+ + ", message: " + conn.getResponseMessage());
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticationException.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticationException.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticationException.java
new file mode 100644
index 0000000..38e5f6a
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/AuthenticationException.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.kerby.has.common.spnego;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * Exception thrown when an authentication error occurrs.
+ */
+public class AuthenticationException extends Exception {
+
+ static final long serialVersionUID = 0;
+
+ /**
+ * Creates an {@link AuthenticationException}.
+ *
+ * @param cause original exception.
+ */
+ public AuthenticationException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates an {@link AuthenticationException}.
+ *
+ * @param msg exception message.
+ */
+ public AuthenticationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Creates an {@link AuthenticationException}.
+ *
+ * @param msg exception message.
+ * @param cause original exception.
+ */
+ public AuthenticationException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/Authenticator.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/Authenticator.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/Authenticator.java
new file mode 100644
index 0000000..a643218
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/Authenticator.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.kerby.has.common.spnego;
+
+import org.apache.kerby.has.common.util.ConnectionConfigurator;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * Interface for client authentication mechanisms.
+ * <p>
+ * Implementations are use-once instances, they don't need to be thread safe.
+ */
+public interface Authenticator {
+
+ /**
+ * Sets a {@link ConnectionConfigurator} instance to use for
+ * configuring connections.
+ *
+ * @param configurator the {@link ConnectionConfigurator} instance.
+ */
+ void setConnectionConfigurator(ConnectionConfigurator configurator);
+
+ /**
+ * Authenticates against a URL and returns a {@link AuthenticatedURL.Token} to be
+ * used by subsequent requests.
+ *
+ * @param url the URl to authenticate against.
+ * @param token the authentication token being used for the user.
+ *
+ * @throws IOException if an IO error occurred.
+ * @throws AuthenticationException if an authentication error occurred.
+ */
+ void authenticate(URL url, AuthenticatedURL.Token token) throws IOException, AuthenticationException;
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosAuthenticator.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosAuthenticator.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosAuthenticator.java
new file mode 100644
index 0000000..a62383b
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosAuthenticator.java
@@ -0,0 +1,360 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.kerby.has.common.spnego;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.kerby.has.common.util.ConnectionConfigurator;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosKey;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.kerby.has.common.util.PlatformName.IBM_JAVA;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * The {@link KerberosAuthenticator} implements the Kerberos SPNEGO authentication sequence.
+ * <p>
+ * It uses the default principal for the Kerberos cache (normally set via kinit).
+ * <p>
+ */
+@SuppressWarnings("PMD")
+public class KerberosAuthenticator implements Authenticator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(KerberosAuthenticator.class);
+
+ /**
+ * HTTP header used by the SPNEGO server endpoint during an authentication sequence.
+ */
+ public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
+
+ /**
+ * HTTP header used by the SPNEGO client endpoint during an authentication sequence.
+ */
+ public static final String AUTHORIZATION = "Authorization";
+
+ /**
+ * HTTP header prefix used by the SPNEGO client/server endpoints during an authentication sequence.
+ */
+ public static final String NEGOTIATE = "Negotiate";
+
+ private static final String AUTH_HTTP_METHOD = "OPTIONS";
+
+ private static String keytabPrincipal = null;
+ private static String keytabFile = null;
+
+ /*
+ * Defines the Kerberos configuration that will be used to obtain the Kerberos principal from the
+ * Kerberos cache.
+ */
+ private static class KerberosConfiguration extends Configuration {
+
+ private static final String OS_LOGIN_MODULE_NAME;
+ private static final boolean WINDOWS = System.getProperty("os.name").startsWith("Windows");
+ private static final boolean IS_64_BIT = System.getProperty("os.arch").contains("64");
+ private static final boolean AIX = System.getProperty("os.name").equals("AIX");
+
+ /* Return the OS login module class name */
+ private static String getOSLoginModuleName() {
+ if (IBM_JAVA) {
+ if (WINDOWS) {
+ return IS_64_BIT ? "com.ibm.security.auth.module.Win64LoginModule"
+ : "com.ibm.security.auth.module.NTLoginModule";
+ } else if (AIX) {
+ return IS_64_BIT ? "com.ibm.security.auth.module.AIX64LoginModule"
+ : "com.ibm.security.auth.module.AIXLoginModule";
+ } else {
+ return "com.ibm.security.auth.module.LinuxLoginModule";
+ }
+ } else {
+ return WINDOWS ? "com.sun.security.auth.module.NTLoginModule"
+ : "com.sun.security.auth.module.UnixLoginModule";
+ }
+ }
+
+ static {
+ OS_LOGIN_MODULE_NAME = getOSLoginModuleName();
+ }
+
+ private static final AppConfigurationEntry OS_SPECIFIC_LOGIN =
+ new AppConfigurationEntry(OS_LOGIN_MODULE_NAME,
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ new HashMap<String, String>());
+
+ private static final Map<String, String> KEYTAB_KERBEROS_OPTIONS
+ = new HashMap<String, String>();
+ static {
+ if (IBM_JAVA) {
+ KEYTAB_KERBEROS_OPTIONS.put("credsType", "both");
+ KEYTAB_KERBEROS_OPTIONS.put("useKeytab",
+ prependFileAuthority(keytabFile));
+ } else {
+ KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
+ KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
+ KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true");
+ KEYTAB_KERBEROS_OPTIONS.put("keyTab", keytabFile);
+ }
+ KEYTAB_KERBEROS_OPTIONS.put("principal", keytabPrincipal);
+ KEYTAB_KERBEROS_OPTIONS.put("refreshKrb5Config", "true");
+ KEYTAB_KERBEROS_OPTIONS.put("debug", "false");
+ }
+
+ private static final AppConfigurationEntry USER_KERBEROS_LOGIN =
+ new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(),
+ AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL,
+ KEYTAB_KERBEROS_OPTIONS);
+
+ private static final AppConfigurationEntry[] USER_KERBEROS_CONF =
+ new AppConfigurationEntry[]{OS_SPECIFIC_LOGIN, USER_KERBEROS_LOGIN};
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
+ return USER_KERBEROS_CONF;
+ }
+
+ private static String prependFileAuthority(String keytabPath) {
+ return keytabPath.startsWith("file://") ? keytabPath
+ : "file://" + keytabPath;
+ }
+ }
+
+ private URL url;
+ private HttpURLConnection conn;
+ private Base64 base64;
+ private ConnectionConfigurator connConfigurator;
+
+ /**
+ * Sets a {@link ConnectionConfigurator} instance to use for
+ * configuring connections.
+ *
+ * @param configurator the {@link ConnectionConfigurator} instance.
+ */
+ @Override
+ public void setConnectionConfigurator(ConnectionConfigurator configurator) {
+ connConfigurator = configurator;
+ }
+
+ /**
+ * Performs SPNEGO authentication against the specified URL.
+ * <p>
+ * If a token is given it does a NOP and returns the given token.
+ * <p>
+ * If no token is given, it will perform the SPNEGO authentication sequence using an
+ * HTTP <code>OPTIONS</code> request.
+ *
+ * @param url the URl to authenticate against.
+ * @param token the authentication token being used for the user.
+ *
+ * @throws IOException if an IO error occurred.
+ * @throws AuthenticationException if an authentication error occurred.
+ */
+ @Override
+ public void authenticate(URL url, AuthenticatedURL.Token token)
+ throws IOException, AuthenticationException {
+
+ if (!token.isSet()) {
+ this.url = url;
+ base64 = new Base64(0);
+ conn = (HttpURLConnection) url.openConnection();
+ if (connConfigurator != null) {
+ conn = connConfigurator.configure(conn);
+ }
+ conn.setRequestMethod(AUTH_HTTP_METHOD);
+ conn.connect();
+
+ boolean needFallback = false;
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ LOG.debug("JDK performed authentication on our behalf.");
+ // If the JDK already did the SPNEGO back-and-forth for
+ // us, just pull out the token.
+ AuthenticatedURL.extractToken(conn, token);
+ if (isTokenKerberos(token)) {
+ return;
+ }
+ needFallback = true;
+ }
+ if (!needFallback && isNegotiate()) {
+ LOG.debug("Performing our own SPNEGO sequence.");
+ doSpnegoSequence(token);
+ } else {
+ throw new IOException("Should perform our own SPNEGO sequence");
+ }
+ }
+ }
+
+ public void setKeyTab(String keytabFile, String keytabPrincipal) {
+ this.keytabFile = keytabFile;
+ this.keytabPrincipal = keytabPrincipal;
+ }
+
+ /*
+ * Check if the passed token is of type "kerberos" or "kerberos-dt"
+ */
+ private boolean isTokenKerberos(AuthenticatedURL.Token token)
+ throws AuthenticationException {
+ if (token.isSet()) {
+ AuthToken aToken = AuthToken.parse(token.toString());
+ if (aToken.getType().equals("kerberos")
+ || aToken.getType().equals("kerberos-dt")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Indicates if the response is starting a SPNEGO negotiation.
+ */
+ private boolean isNegotiate() throws IOException {
+ boolean negotiate = false;
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
+ negotiate = authHeader != null && authHeader.trim().startsWith(NEGOTIATE);
+ }
+ return negotiate;
+ }
+
+ /**
+ * Implements the SPNEGO authentication sequence interaction using the current default principal
+ * in the Kerberos cache (normally set via kinit).
+ *
+ * @param token the authentication token being used for the user.
+ *
+ * @throws IOException if an IO error occurred.
+ * @throws AuthenticationException if an authentication error occurred.
+ */
+ private void doSpnegoSequence(AuthenticatedURL.Token token) throws IOException, AuthenticationException {
+ try {
+ AccessControlContext context = AccessController.getContext();
+ Subject subject = Subject.getSubject(context);
+ if (subject == null
+ || (subject.getPrivateCredentials(KerberosKey.class).isEmpty()
+ && subject.getPrivateCredentials(KerberosTicket.class).isEmpty())) {
+ LOG.debug("No subject in context, logging in");
+ subject = new Subject();
+ LoginContext login = new LoginContext("", subject,
+ null, new KerberosConfiguration());
+ login.login();
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Using subject: " + subject);
+ }
+ Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
+
+ @Override
+ public Void run() throws Exception {
+ GSSContext gssContext = null;
+ try {
+ GSSManager gssManager = GSSManager.getInstance();
+ String servicePrincipal = KerberosUtil.getServicePrincipal("HTTP",
+ KerberosAuthenticator.this.url.getHost());
+ LOG.info("service principal is:" + servicePrincipal);
+ Oid oid = KerberosUtil.getOidInstance("NT_GSS_KRB5_PRINCIPAL");
+ GSSName serviceName = gssManager.createName(servicePrincipal,
+ oid);
+ oid = KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID");
+ gssContext = gssManager.createContext(serviceName, oid, null,
+ GSSContext.DEFAULT_LIFETIME);
+ gssContext.requestCredDeleg(true);
+ gssContext.requestMutualAuth(true);
+
+ byte[] inToken = new byte[0];
+ byte[] outToken;
+ boolean established = false;
+
+ // Loop while the context is still not established
+ while (!established) {
+ outToken = gssContext.initSecContext(inToken, 0, inToken.length);
+ if (outToken != null) {
+ sendToken(outToken);
+ }
+
+ if (!gssContext.isEstablished()) {
+ inToken = readToken();
+ } else {
+ established = true;
+ }
+ }
+ } finally {
+ if (gssContext != null) {
+ gssContext.dispose();
+ gssContext = null;
+ }
+ }
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException ex) {
+ throw new AuthenticationException(ex.getException());
+ } catch (LoginException ex) {
+ throw new AuthenticationException(ex);
+ }
+ AuthenticatedURL.extractToken(conn, token);
+ }
+
+ /*
+ * Sends the Kerberos token to the server.
+ */
+ private void sendToken(byte[] outToken) throws IOException {
+ String token = base64.encodeToString(outToken);
+ conn = (HttpURLConnection) url.openConnection();
+ if (connConfigurator != null) {
+ conn = connConfigurator.configure(conn);
+ }
+ conn.setRequestMethod(AUTH_HTTP_METHOD);
+ conn.setRequestProperty(AUTHORIZATION, NEGOTIATE + " " + token);
+ conn.connect();
+ }
+
+ /*
+ * Retrieves the Kerberos token returned by the server.
+ */
+ private byte[] readToken() throws IOException, AuthenticationException {
+ int status = conn.getResponseCode();
+ if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
+ if (authHeader == null || !authHeader.trim().startsWith(NEGOTIATE)) {
+ throw new AuthenticationException("Invalid SPNEGO sequence, '" + WWW_AUTHENTICATE
+ + "' header incorrect: " + authHeader);
+ }
+ String negotiation = authHeader.trim().substring((NEGOTIATE + " ").length()).trim();
+ return base64.decode(negotiation);
+ }
+ throw new AuthenticationException("Invalid SPNEGO sequence, status code: " + status);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosHasAuthenticator.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosHasAuthenticator.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosHasAuthenticator.java
new file mode 100644
index 0000000..da598a3
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosHasAuthenticator.java
@@ -0,0 +1,25 @@
+/**
+ * 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.kerby.has.common.spnego;
+
+public class KerberosHasAuthenticator extends KerberosAuthenticator {
+
+ public KerberosHasAuthenticator(String keytabFile, String keytabPrincipal) {
+ setKeyTab(keytabFile, keytabPrincipal);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosUtil.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosUtil.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosUtil.java
new file mode 100644
index 0000000..14e30dd
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/spnego/KerberosUtil.java
@@ -0,0 +1,262 @@
+/**
+ * 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.kerby.has.common.spnego;
+
+import org.apache.kerby.kerberos.kerb.keytab.Keytab;
+import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.Oid;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.kerberos.KeyTab;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import static org.apache.kerby.has.common.util.PlatformName.IBM_JAVA;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+@SuppressWarnings("PMD")
+public class KerberosUtil {
+
+ /* Return the Kerberos login module name */
+ public static String getKrb5LoginModuleName() {
+ return (IBM_JAVA)
+ ? "com.ibm.security.auth.module.Krb5LoginModule"
+ : "com.sun.security.auth.module.Krb5LoginModule";
+ }
+
+ public static Oid getOidInstance(String oidName)
+ throws ClassNotFoundException, GSSException, NoSuchFieldException,
+ IllegalAccessException {
+ Class<?> oidClass;
+ if (IBM_JAVA) {
+ if ("NT_GSS_KRB5_PRINCIPAL".equals(oidName)) {
+ // IBM JDK GSSUtil class does not have field for krb5 principal oid
+ return new Oid("1.2.840.113554.1.2.2.1");
+ }
+ oidClass = Class.forName("com.ibm.security.jgss.GSSUtil");
+ } else {
+ oidClass = Class.forName("sun.security.jgss.GSSUtil");
+ }
+ Field oidField = oidClass.getDeclaredField(oidName);
+ return (Oid) oidField.get(oidClass);
+ }
+
+ public static String getDefaultRealm()
+ throws ClassNotFoundException, NoSuchMethodException,
+ IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException {
+ Object kerbConf;
+ Class<?> classRef;
+ Method getInstanceMethod;
+ Method getDefaultRealmMethod;
+ if (IBM_JAVA) {
+ classRef = Class.forName("com.ibm.security.krb5.internal.Config");
+ } else {
+ classRef = Class.forName("sun.security.krb5.Config");
+ }
+ getInstanceMethod = classRef.getMethod("getInstance", new Class[0]);
+ kerbConf = getInstanceMethod.invoke(classRef, new Object[0]);
+ getDefaultRealmMethod = classRef.getDeclaredMethod("getDefaultRealm",
+ new Class[0]);
+ return (String) getDefaultRealmMethod.invoke(kerbConf, new Object[0]);
+ }
+
+ public static String getDefaultRealmProtected() {
+ String realmString = null;
+ try {
+ realmString = getDefaultRealm();
+ } catch (RuntimeException rte) {
+ //silently catch everything
+ } catch (Exception e) {
+ //silently return null
+ }
+ return realmString;
+ }
+
+ /*
+ * For a Service Host Principal specification, map the host's domain
+ * to kerberos realm, as specified by krb5.conf [domain_realm] mappings.
+ * Unfortunately the mapping routines are private to the security.krb5
+ * package, so have to construct a PrincipalName instance to derive the realm.
+ *
+ * Many things can go wrong with Kerberos configuration, and this is not
+ * the place to be throwing exceptions to help debug them. Nor do we choose
+ * to make potentially voluminous logs on every call to a communications API.
+ * So we simply swallow all exceptions from the underlying libraries and
+ * return null if we can't get a good value for the realmString.
+ *
+ * @param shortprinc A service principal name with host fqdn as instance, e.g.
+ * "HTTP/myhost.mydomain"
+ * @return String value of Kerberos realm, mapped from host fqdn
+ * May be default realm, or may be null.
+ */
+ public static String getDomainRealm(String shortprinc) {
+ Class<?> classRef;
+ Object principalName; //of type sun.security.krb5.PrincipalName or IBM equiv
+ String realmString = null;
+ try {
+ if (IBM_JAVA) {
+ classRef = Class.forName("com.ibm.security.krb5.PrincipalName");
+ } else {
+ classRef = Class.forName("sun.security.krb5.PrincipalName");
+ }
+ int tKrbNtSrvHst = classRef.getField("KRB_NT_SRV_HST").getInt(null);
+ principalName = classRef.getConstructor(String.class, int.class).
+ newInstance(shortprinc, tKrbNtSrvHst);
+ realmString = (String) classRef.getMethod("getRealmString", new Class[0]).
+ invoke(principalName, new Object[0]);
+ } catch (RuntimeException rte) {
+ //silently catch everything
+ } catch (Exception e) {
+ //silently return default realm (which may itself be null)
+ }
+ if (null == realmString || realmString.equals("")) {
+ return getDefaultRealmProtected();
+ } else {
+ return realmString;
+ }
+ }
+
+ /* Return fqdn of the current host */
+ static String getLocalHostName() throws UnknownHostException {
+ return InetAddress.getLocalHost().getCanonicalHostName();
+ }
+
+ /**
+ * Create Kerberos principal for a given service and hostname,
+ * inferring realm from the fqdn of the hostname. It converts
+ * hostname to lower case. If hostname is null or "0.0.0.0", it uses
+ * dynamically looked-up fqdn of the current host instead.
+ * If domain_realm mappings are inadequately specified, it will
+ * use default_realm, per usual Kerberos behavior.
+ * If default_realm also gives a null value, then a principal
+ * without realm will be returned, which by Kerberos definitions is
+ * just another way to specify default realm.
+ *
+ * @param service
+ * Service for which you want to generate the principal.
+ * @param hostname
+ * Fully-qualified domain name.
+ * @return Converted Kerberos principal name.
+ * @throws UnknownHostException
+ * If no IP address for the local host could be found.
+ */
+ public static final String getServicePrincipal(String service,
+ String hostname)
+ throws UnknownHostException {
+ String fqdn = hostname;
+ String shortprinc = null;
+ String realmString = null;
+ if (null == fqdn || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
+ fqdn = getLocalHostName();
+ }
+ // convert hostname to lowercase as kerberos does not work with hostnames
+ // with uppercase characters.
+ fqdn = fqdn.toLowerCase(Locale.US);
+ shortprinc = service + "/" + fqdn;
+ // Obtain the realm name inferred from the domain of the host
+ realmString = getDomainRealm(shortprinc);
+ if (null == realmString || realmString.equals("")) {
+ return shortprinc;
+ } else {
+ return shortprinc + "@" + realmString;
+ }
+ }
+
+ /**
+ * Get all the unique principals present in the keytabfile.
+ *
+ * @param keytabFileName
+ * Name of the keytab file to be read.
+ * @return list of unique principals in the keytab.
+ * @throws IOException
+ * If keytab entries cannot be read from the file.
+ */
+ static final String[] getPrincipalNames(String keytabFileName) throws IOException {
+ Keytab keytab = Keytab.loadKeytab(new File(keytabFileName));
+ Set<String> principals = new HashSet<String>();
+ List<PrincipalName> entries = keytab.getPrincipals();
+ for (PrincipalName entry : entries) {
+ principals.add(entry.getName().replace("\\", "/"));
+ }
+ return principals.toArray(new String[0]);
+ }
+
+ /**
+ * Get all the unique principals from keytabfile which matches a pattern.
+ *
+ * @param keytab Name of the keytab file to be read.
+ * @param pattern pattern to be matched.
+ * @return list of unique principals which matches the pattern.
+ * @throws IOException if cannot get the principal name
+ */
+ public static final String[] getPrincipalNames(String keytab,
+ Pattern pattern) throws IOException {
+ String[] principals = getPrincipalNames(keytab);
+ if (principals.length != 0) {
+ List<String> matchingPrincipals = new ArrayList<String>();
+ for (String principal : principals) {
+ if (pattern.matcher(principal).matches()) {
+ matchingPrincipals.add(principal);
+ }
+ }
+ principals = matchingPrincipals.toArray(new String[0]);
+ }
+ return principals;
+ }
+
+ /**
+ * Check if the subject contains Kerberos keytab related objects.
+ * The Kerberos keytab object attached in subject has been changed
+ * from KerberosKey (JDK 7) to KeyTab (JDK 8)
+ *
+ *
+ * @param subject subject to be checked
+ * @return true if the subject contains Kerberos keytab
+ */
+ public static boolean hasKerberosKeyTab(Subject subject) {
+ return !subject.getPrivateCredentials(KeyTab.class).isEmpty();
+ }
+
+ /**
+ * Check if the subject contains Kerberos ticket.
+ *
+ *
+ * @param subject subject to be checked
+ * @return true if the subject contains Kerberos ticket
+ */
+ public static boolean hasKerberosTicket(Subject subject) {
+ return !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/KeyStoresFactory.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/KeyStoresFactory.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/KeyStoresFactory.java
new file mode 100644
index 0000000..0c48fff
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/KeyStoresFactory.java
@@ -0,0 +1,252 @@
+/**
+* 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.kerby.has.common.ssl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.util.StringUtils;
+import org.apache.kerby.kerberos.kerb.client.KrbConfig;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManager;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.text.MessageFormat;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * Interface that gives access to {@link KeyManager} and {@link TrustManager}
+ * implementations.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class KeyStoresFactory extends KrbConfig {
+
+ private static final Log LOG =
+ LogFactory.getLog(KeyStoresFactory.class);
+
+ public static final String SSL_KEYSTORE_LOCATION_TPL_KEY =
+ "ssl.{0}.keystore.location";
+ public static final String SSL_KEYSTORE_PASSWORD_TPL_KEY =
+ "ssl.{0}.keystore.password";
+ public static final String SSL_KEYSTORE_KEYPASSWORD_TPL_KEY =
+ "ssl.{0}.keystore.keypassword";
+ public static final String SSL_KEYSTORE_TYPE_TPL_KEY =
+ "ssl.{0}.keystore.type";
+
+ public static final String SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY =
+ "ssl.{0}.truststore.reload.interval";
+ public static final String SSL_TRUSTSTORE_LOCATION_TPL_KEY =
+ "ssl.{0}.truststore.location";
+ public static final String SSL_TRUSTSTORE_PASSWORD_TPL_KEY =
+ "ssl.{0}.truststore.password";
+ public static final String SSL_TRUSTSTORE_TYPE_TPL_KEY =
+ "ssl.{0}.truststore.type";
+
+ /**
+ * Default format of the keystore files.
+ */
+ public static final String DEFAULT_KEYSTORE_TYPE = "jks";
+
+ /**
+ * Reload interval in milliseconds.
+ */
+ public static final long DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL = 10000;
+
+ private HasConfig conf;
+ private KeyManager[] keyManagers;
+ private TrustManager[] trustManagers;
+ private ReloadingX509TrustManager trustManager;
+
+ /**
+ * Sets the configuration for the factory.
+ *
+ * @param conf the configuration for the factory.
+ */
+ public void setConf(HasConfig conf) {
+ this.conf = conf;
+ }
+
+ /**
+ * Returns the configuration of the factory.
+ *
+ * @return the configuration of the factory.
+ */
+ public HasConfig getConf() {
+ return conf;
+ }
+
+
+ /**
+ * Initializes the keystores of the factory.
+ *
+ * @param mode if the keystores are to be used in client or server mode.
+ * @throws IOException thrown if the keystores could not be initialized due
+ * to an IO error.
+ * @throws GeneralSecurityException thrown if the keystores could not be
+ * initialized due to an security error.
+ */
+ public void init(SSLFactory.Mode mode) throws IOException, GeneralSecurityException {
+ boolean requireClientCert =
+ conf.getBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY,
+ SSLFactory.DEFAULT_SSL_REQUIRE_CLIENT_CERT);
+
+ // certificate store
+ String keystoreType =
+ conf.getString(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
+ DEFAULT_KEYSTORE_TYPE);
+ KeyStore keystore = KeyStore.getInstance(keystoreType);
+ String keystoreKeyPassword = null;
+ if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
+ String locationProperty =
+ resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
+ String keystoreLocation = conf.getString(locationProperty, "");
+ if (keystoreLocation.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + locationProperty
+ + "' has not been set in the ssl configuration file.");
+ }
+ String passwordProperty =
+ resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
+ String keystorePassword = getPassword(conf, passwordProperty, "");
+ if (keystorePassword.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + passwordProperty
+ + "' has not been set in the ssl configuration file.");
+ }
+ String keyPasswordProperty =
+ resolvePropertyName(mode, SSL_KEYSTORE_KEYPASSWORD_TPL_KEY);
+ // Key password defaults to the same value as store password for
+ // compatibility with legacy configurations that did not use a separate
+ // configuration property for key password.
+ keystoreKeyPassword = getPassword(
+ conf, keyPasswordProperty, keystorePassword);
+ LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
+
+ InputStream is = new FileInputStream(keystoreLocation);
+ try {
+ keystore.load(is, keystorePassword.toCharArray());
+ } finally {
+ is.close();
+ }
+ LOG.debug(mode.toString() + " Loaded KeyStore: " + keystoreLocation);
+ } else {
+ keystore.load(null, null);
+ }
+ KeyManagerFactory keyMgrFactory = KeyManagerFactory
+ .getInstance(SSLFactory.SSLCERTIFICATE);
+
+ keyMgrFactory.init(keystore, (keystoreKeyPassword != null)
+ ? keystoreKeyPassword.toCharArray() : null);
+ keyManagers = keyMgrFactory.getKeyManagers();
+
+ //trust store
+ String truststoreType =
+ conf.getString(resolvePropertyName(mode, SSL_TRUSTSTORE_TYPE_TPL_KEY),
+ DEFAULT_KEYSTORE_TYPE);
+
+ String locationProperty =
+ resolvePropertyName(mode, SSL_TRUSTSTORE_LOCATION_TPL_KEY);
+ String truststoreLocation = conf.getString(locationProperty, "");
+ if (!truststoreLocation.isEmpty()) {
+ String passwordProperty = resolvePropertyName(mode,
+ SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
+ String truststorePassword = getPassword(conf, passwordProperty, "");
+ if (truststorePassword.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + passwordProperty
+ + "' has not been set in the ssl configuration file.");
+ }
+ long truststoreReloadInterval =
+ conf.getLong(resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
+ DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL);
+
+ LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation);
+
+ trustManager = new ReloadingX509TrustManager(truststoreType,
+ truststoreLocation,
+ truststorePassword,
+ truststoreReloadInterval);
+ trustManager.init();
+ LOG.debug(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
+ trustManagers = new TrustManager[]{trustManager};
+ } else {
+ LOG.debug("The property '" + locationProperty + "' has not been set, "
+ + "no TrustStore will be loaded");
+ trustManagers = null;
+ }
+ }
+
+ String getPassword(HasConfig conf, String alias, String defaultPass) {
+ String password = defaultPass;
+ password = conf.getString(alias);
+ return password;
+ }
+
+ /**
+ * Releases any resources being used.
+ */
+ public void destroy() {
+ if (trustManager != null) {
+ trustManager.destroy();
+ trustManager = null;
+ keyManagers = null;
+ trustManagers = null;
+ }
+ }
+
+ /**
+ * Returns the keymanagers for owned certificates.
+ *
+ * @return the keymanagers for owned certificates.
+ */
+ public KeyManager[] getKeyManagers() {
+ return keyManagers;
+ }
+
+ /**
+ * Returns the trustmanagers for trusted certificates.
+ *
+ * @return the trustmanagers for trusted certificates.
+ */
+ public TrustManager[] getTrustManagers() {
+ return trustManagers;
+ }
+
+ /**
+ * Resolves a property name to its client/server version if applicable.
+ * <p/>
+ * NOTE: This method is public for testing purposes.
+ *
+ * @param mode client/server mode.
+ * @param template property name template.
+ * @return the resolved property name.
+ */
+ public static String resolvePropertyName(SSLFactory.Mode mode,
+ String template) {
+ return MessageFormat.format(
+ template, StringUtils.toLowerCase(mode.toString()));
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/ReloadingX509TrustManager.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/ReloadingX509TrustManager.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/ReloadingX509TrustManager.java
new file mode 100644
index 0000000..29ed038
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/ReloadingX509TrustManager.java
@@ -0,0 +1,208 @@
+/**
+ * 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.kerby.has.common.ssl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * A {@link TrustManager} implementation that reloads its configuration when
+ * the truststore file on disk changes.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public final class ReloadingX509TrustManager
+ implements X509TrustManager, Runnable {
+
+ private static final Log LOG =
+ LogFactory.getLog(ReloadingX509TrustManager.class);
+
+ private String type;
+ private File file;
+ private String password;
+ private long lastLoaded;
+ private long reloadInterval;
+ private AtomicReference<X509TrustManager> trustManagerRef;
+
+ private volatile boolean running;
+ private Thread reloader;
+
+ /**
+ * Creates a reloadable trustmanager. The trustmanager reloads itself
+ * if the underlying trustore file has changed.
+ *
+ * @param type type of truststore file, typically 'jks'.
+ * @param location local path to the truststore file.
+ * @param password password of the truststore file.
+ * @param reloadInterval interval to check if the truststore file has
+ * changed, in milliseconds.
+ * @throws IOException thrown if the truststore could not be initialized due
+ * to an IO error.
+ * @throws GeneralSecurityException thrown if the truststore could not be
+ * initialized due to a security error.
+ */
+ public ReloadingX509TrustManager(String type, String location,
+ String password, long reloadInterval)
+ throws IOException, GeneralSecurityException {
+ this.type = type;
+ file = new File(location);
+ this.password = password;
+ trustManagerRef = new AtomicReference<X509TrustManager>();
+ trustManagerRef.set(loadTrustManager());
+ this.reloadInterval = reloadInterval;
+ }
+
+ /**
+ * Starts the reloader thread.
+ */
+ public void init() {
+ reloader = new Thread(this, "Truststore reloader thread");
+ reloader.setDaemon(true);
+ running = true;
+ reloader.start();
+ }
+
+ /**
+ * Stops the reloader thread.
+ */
+ public void destroy() {
+ running = false;
+ reloader.interrupt();
+ }
+
+ /**
+ * Returns the reload check interval.
+ *
+ * @return the reload check interval, in milliseconds.
+ */
+ public long getReloadInterval() {
+ return reloadInterval;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ X509TrustManager tm = trustManagerRef.get();
+ if (tm != null) {
+ tm.checkClientTrusted(chain, authType);
+ } else {
+ throw new CertificateException("Unknown client chain certificate: "
+ + chain[0].toString());
+ }
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ X509TrustManager tm = trustManagerRef.get();
+ if (tm != null) {
+ tm.checkServerTrusted(chain, authType);
+ } else {
+ throw new CertificateException("Unknown server chain certificate: "
+ + chain[0].toString());
+ }
+ }
+
+ private static final X509Certificate[] EMPTY = new X509Certificate[0];
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ X509Certificate[] issuers = EMPTY;
+ X509TrustManager tm = trustManagerRef.get();
+ if (tm != null) {
+ issuers = tm.getAcceptedIssuers();
+ }
+ return issuers;
+ }
+
+ boolean needsReload() {
+ boolean reload = true;
+ if (file.exists()) {
+ if (file.lastModified() == lastLoaded) {
+ reload = false;
+ }
+ } else {
+ lastLoaded = 0;
+ }
+ return reload;
+ }
+
+ X509TrustManager loadTrustManager()
+ throws IOException, GeneralSecurityException {
+ X509TrustManager trustManager = null;
+ KeyStore ks = KeyStore.getInstance(type);
+ lastLoaded = file.lastModified();
+ FileInputStream in = new FileInputStream(file);
+ try {
+ ks.load(in, password.toCharArray());
+ LOG.debug("Loaded truststore '" + file + "'");
+ } finally {
+ in.close();
+ }
+
+ TrustManagerFactory trustManagerFactory =
+ TrustManagerFactory.getInstance(SSLFactory.SSLCERTIFICATE);
+ trustManagerFactory.init(ks);
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ for (TrustManager trustManager1 : trustManagers) {
+ if (trustManager1 instanceof X509TrustManager) {
+ trustManager = (X509TrustManager) trustManager1;
+ break;
+ }
+ }
+ return trustManager;
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ try {
+ Thread.sleep(reloadInterval);
+ } catch (InterruptedException e) {
+ //NOP
+ }
+ if (running && needsReload()) {
+ try {
+ trustManagerRef.set(loadTrustManager());
+ } catch (Exception ex) {
+ LOG.warn("Could not load truststore (keep using existing one) : "
+ + ex.toString(), ex);
+ }
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLFactory.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLFactory.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLFactory.java
new file mode 100644
index 0000000..874ff08
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLFactory.java
@@ -0,0 +1,290 @@
+/**
+* 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.kerby.has.common.ssl;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.common.util.ConnectionConfigurator;
+import org.apache.kerby.has.common.util.StringUtils;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.security.GeneralSecurityException;
+
+import static org.apache.kerby.has.common.util.PlatformName.IBM_JAVA;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * Factory that creates SSLEngine and SSLSocketFactory instances using
+ * Hadoop configuration information.
+ * <p/>
+ * which reloads public keys if the truststore file changes.
+ * <p/>
+ * This factory is used to configure HTTPS in Hadoop HTTP based endpoints, both
+ * client and server.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class SSLFactory implements ConnectionConfigurator {
+
+ @InterfaceAudience.Private
+ public enum Mode {
+ CLIENT, SERVER
+ }
+
+ public static final String SSL_REQUIRE_CLIENT_CERT_KEY =
+ "hadoop.ssl.require.client.CERT";
+ public static final String SSL_HOSTNAME_VERIFIER_KEY =
+ "hadoop.ssl.hostname.verifier";
+ public static final String SSL_CLIENT_CONF_KEY =
+ "hadoop.ssl.client.conf";
+ public static final String SSL_SERVER_CONF_KEY =
+ "hadoop.ssl.server.conf";
+ public static final String SSLCERTIFICATE = IBM_JAVA ? "ibmX509" : "SunX509";
+
+ public static final boolean DEFAULT_SSL_REQUIRE_CLIENT_CERT = false;
+
+ public static final String KEYSTORES_FACTORY_CLASS_KEY =
+ "hadoop.ssl.keystores.factory.class";
+
+ public static final String SSL_ENABLED_PROTOCOLS =
+ "hadoop.ssl.enabled.protocols";
+ public static final String DEFAULT_SSL_ENABLED_PROTOCOLS = "TLSv1";
+
+ private HasConfig conf;
+ private Mode mode;
+ private boolean requireClientCert;
+ private SSLContext context;
+ private HostnameVerifier hostnameVerifier;
+ private KeyStoresFactory keystoresFactory;
+
+ private String[] enabledProtocols = null;
+
+ /**
+ * Creates an SSLFactory.
+ *
+ * @param mode SSLFactory mode, client or server.
+ * @param conf Hadoop configuration from where the SSLFactory configuration
+ * will be read.
+ */
+ public SSLFactory(Mode mode, HasConfig conf) throws HasException {
+ this.conf = conf;
+ if (mode == null) {
+ throw new IllegalArgumentException("mode cannot be NULL");
+ }
+ this.mode = mode;
+ requireClientCert = conf.getBoolean(SSL_REQUIRE_CLIENT_CERT_KEY,
+ DEFAULT_SSL_REQUIRE_CLIENT_CERT);
+ HasConfig sslConf = readSSLConfiguration(mode);
+
+ keystoresFactory = new KeyStoresFactory();
+ keystoresFactory.setConf(sslConf);
+
+ enabledProtocols = new String[] {DEFAULT_SSL_ENABLED_PROTOCOLS};
+ }
+
+ private HasConfig readSSLConfiguration(Mode mode) throws HasException {
+ HasConfig sslConf = new HasConfig();
+ sslConf.setBoolean(SSL_REQUIRE_CLIENT_CERT_KEY, requireClientCert);
+ String sslConfResource;
+ if (mode == Mode.CLIENT) {
+ sslConfResource = conf.getString(SSLFactory.SSL_CLIENT_CONF_KEY);
+ } else {
+ sslConfResource = conf.getString(SSLFactory.SSL_CLIENT_CONF_KEY);
+ }
+ try {
+ sslConf.addIniConfig(new File(sslConfResource));
+ } catch (IOException e) {
+ throw new HasException(e);
+ }
+ return sslConf;
+ }
+
+ /**
+ * Initializes the factory.
+ *
+ * @throws GeneralSecurityException thrown if an SSL initialization error
+ * happened.
+ * @throws IOException thrown if an IO error happened while reading the SSL
+ * configuration.
+ */
+ public void init() throws GeneralSecurityException, IOException {
+ keystoresFactory.init(mode);
+ context = SSLContext.getInstance("TLS");
+ context.init(keystoresFactory.getKeyManagers(),
+ keystoresFactory.getTrustManagers(), null);
+ context.getDefaultSSLParameters().setProtocols(enabledProtocols);
+ hostnameVerifier = getHostnameVerifier(conf);
+ }
+
+ private HostnameVerifier getHostnameVerifier(HasConfig conf)
+ throws GeneralSecurityException, IOException {
+ return getHostnameVerifier(StringUtils.toUpperCase(
+ conf.getString(SSL_HOSTNAME_VERIFIER_KEY, "DEFAULT").trim()));
+ }
+
+ public static HostnameVerifier getHostnameVerifier(String verifier)
+ throws GeneralSecurityException, IOException {
+ HostnameVerifier hostnameVerifier;
+ if (verifier.equals("DEFAULT")) {
+ hostnameVerifier = SSLHostnameVerifier.DEFAULT;
+ } else if (verifier.equals("DEFAULT_AND_LOCALHOST")) {
+ hostnameVerifier = SSLHostnameVerifier.DEFAULT_AND_LOCALHOST;
+ } else if (verifier.equals("STRICT")) {
+ hostnameVerifier = SSLHostnameVerifier.STRICT;
+ } else if (verifier.equals("STRICT_IE6")) {
+ hostnameVerifier = SSLHostnameVerifier.STRICT_IE6;
+ } else if (verifier.equals("ALLOW_ALL")) {
+ hostnameVerifier = SSLHostnameVerifier.ALLOW_ALL;
+ } else {
+ throw new GeneralSecurityException("Invalid hostname verifier: "
+ + verifier);
+ }
+ return hostnameVerifier;
+ }
+
+ /**
+ * Releases any resources being used.
+ */
+ public void destroy() {
+ keystoresFactory.destroy();
+ }
+ /**
+ * Returns the SSLFactory KeyStoresFactory instance.
+ *
+ * @return the SSLFactory KeyStoresFactory instance.
+ */
+ public KeyStoresFactory getKeystoresFactory() {
+ return keystoresFactory;
+ }
+
+ /**
+ * Returns a configured SSLEngine.
+ *
+ * @return the configured SSLEngine.
+ * @throws GeneralSecurityException thrown if the SSL engine could not
+ * be initialized.
+ * @throws IOException thrown if and IO error occurred while loading
+ * the server keystore.
+ */
+ public SSLEngine createSSLEngine()
+ throws GeneralSecurityException, IOException {
+ SSLEngine sslEngine = context.createSSLEngine();
+ if (mode == Mode.CLIENT) {
+ sslEngine.setUseClientMode(true);
+ } else {
+ sslEngine.setUseClientMode(false);
+ sslEngine.setNeedClientAuth(requireClientCert);
+ }
+ sslEngine.setEnabledProtocols(enabledProtocols);
+ return sslEngine;
+ }
+
+ /**
+ * Returns a configured SSLServerSocketFactory.
+ *
+ * @return the configured SSLSocketFactory.
+ * @throws GeneralSecurityException thrown if the SSLSocketFactory could not
+ * be initialized.
+ * @throws IOException thrown if and IO error occurred while loading
+ * the server keystore.
+ */
+ public SSLServerSocketFactory createSSLServerSocketFactory()
+ throws GeneralSecurityException, IOException {
+ if (mode != Mode.SERVER) {
+ throw new IllegalStateException("Factory is in CLIENT mode");
+ }
+ return context.getServerSocketFactory();
+ }
+
+ /**
+ * Returns a configured SSLSocketFactory.
+ *
+ * @return the configured SSLSocketFactory.
+ * @throws GeneralSecurityException thrown if the SSLSocketFactory could not
+ * be initialized.
+ * @throws IOException thrown if and IO error occurred while loading
+ * the server keystore.
+ */
+ public SSLSocketFactory createSSLSocketFactory()
+ throws GeneralSecurityException, IOException {
+ if (mode != Mode.CLIENT) {
+ throw new IllegalStateException("Factory is in CLIENT mode");
+ }
+ return context.getSocketFactory();
+ }
+
+ /**
+ * Returns the hostname verifier it should be used in HttpsURLConnections.
+ *
+ * @return the hostname verifier.
+ */
+ public HostnameVerifier getHostnameVerifier() {
+ if (mode != Mode.CLIENT) {
+ throw new IllegalStateException("Factory is in CLIENT mode");
+ }
+ return hostnameVerifier;
+ }
+
+ /**
+ * Returns if client certificates are required or not.
+ *
+ * @return if client certificates are required or not.
+ */
+ public boolean isClientCertRequired() {
+ return requireClientCert;
+ }
+
+ /**
+ * If the given {@link HttpURLConnection} is an {@link HttpsURLConnection}
+ * configures the connection with the {@link SSLSocketFactory} and
+ * {@link HostnameVerifier} of this SSLFactory, otherwise does nothing.
+ *
+ * @param conn the {@link HttpURLConnection} instance to configure.
+ * @return the configured {@link HttpURLConnection} instance.
+ *
+ * @throws IOException if an IO error occurred.
+ */
+ @Override
+ public HttpURLConnection configure(HttpURLConnection conn)
+ throws IOException {
+ if (conn instanceof HttpsURLConnection) {
+ HttpsURLConnection sslConn = (HttpsURLConnection) conn;
+ try {
+ sslConn.setSSLSocketFactory(createSSLSocketFactory());
+ } catch (GeneralSecurityException ex) {
+ throw new IOException(ex);
+ }
+ sslConn.setHostnameVerifier(getHostnameVerifier());
+ conn = sslConn;
+ }
+ return conn;
+ }
+}
[2/3] directory-kerby git commit: DIRKRB-672 Embed a web server in
KDC to upgrade Kerby KDC.
Posted by pl...@apache.org.
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLHostnameVerifier.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLHostnameVerifier.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLHostnameVerifier.java
new file mode 100644
index 0000000..b474d7b
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/ssl/SSLHostnameVerifier.java
@@ -0,0 +1,615 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ * 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 software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.kerby.has.common.ssl;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.kerby.has.common.util.StringUtils;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ ************************************************************************
+ * Copied from the not-yet-commons-ssl project at
+ * http://juliusdavies.ca/commons-ssl/
+ * This project is not yet in Apache, but it is Apache 2.0 licensed.
+ ************************************************************************
+ * Interface for checking if a hostname matches the names stored inside the
+ * server's X.509 certificate. Correctly implements
+ * javax.net.ssl.HostnameVerifier, but that interface is not recommended.
+ * Instead we added several check() methods that take SSLSocket,
+ * or X509Certificate, or ultimately (they all end up calling this one),
+ * String. (It's easier to supply JUnit with Strings instead of mock
+ * SSLSession objects!)
+ * </p><p>Our check() methods throw exceptions if the name is
+ * invalid, whereas javax.net.ssl.HostnameVerifier just returns true/false.
+ * <p/>
+ * We provide the HostnameVerifier.DEFAULT, HostnameVerifier.STRICT, and
+ * HostnameVerifier.ALLOW_ALL implementations. We also provide the more
+ * specialized HostnameVerifier.DEFAULT_AND_LOCALHOST, as well as
+ * HostnameVerifier.STRICT_IE6. But feel free to define your own
+ * implementations!
+ * <p/>
+ * Inspired by Sebastian Hauer's original StrictSSLProtocolSocketFactory in the
+ * HttpClient "contrib" repository.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+@SuppressWarnings("PMD")
+public interface SSLHostnameVerifier extends javax.net.ssl.HostnameVerifier {
+
+ @Override
+ boolean verify(String host, SSLSession session);
+
+ void check(String host, SSLSocket ssl) throws IOException;
+
+ void check(String host, X509Certificate cert) throws SSLException;
+
+ void check(String host, String[] cns, String[] subjectAlts)
+ throws SSLException;
+
+ void check(String[] hosts, SSLSocket ssl) throws IOException;
+
+ void check(String[] hosts, X509Certificate cert) throws SSLException;
+
+
+ /**
+ * Checks to see if the supplied hostname matches any of the supplied CNs
+ * or "DNS" Subject-Alts. Most implementations only look at the first CN,
+ * and ignore any additional CNs. Most implementations do look at all of
+ * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
+ * according to RFC 2818.
+ *
+ * @param cns CN fields, in order, as extracted from the X.509
+ * certificate.
+ * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
+ * from the X.509 certificate.
+ * @param hosts The array of hostnames to verify.
+ * @throws SSLException If verification failed.
+ */
+ void check(String[] hosts, String[] cns, String[] subjectAlts)
+ throws SSLException;
+
+
+ /**
+ * The DEFAULT HostnameVerifier works the same way as Curl and Firefox.
+ * <p/>
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts.
+ * <p/>
+ * The only difference between DEFAULT and STRICT is that a wildcard (such
+ * as "*.foo.com") with DEFAULT matches all subdomains, including
+ * "a.b.foo.com".
+ */
+ SSLHostnameVerifier DEFAULT =
+ new AbstractVerifier() {
+ @Override
+ public final void check(final String[] hosts, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ check(hosts, cns, subjectAlts, false, false);
+ }
+
+ @Override
+ public final String toString() {
+ return "DEFAULT";
+ }
+ };
+
+
+ /**
+ * The DEFAULT_AND_LOCALHOST HostnameVerifier works like the DEFAULT
+ * one with one additional relaxation: a host of "localhost",
+ * "localhost.localdomain", "127.0.0.1", "::1" will always pass, no matter
+ * what is in the server's certificate.
+ */
+ SSLHostnameVerifier DEFAULT_AND_LOCALHOST =
+ new AbstractVerifier() {
+ @Override
+ public final void check(final String[] hosts, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ if (isLocalhost(hosts[0])) {
+ return;
+ }
+ check(hosts, cns, subjectAlts, false, false);
+ }
+
+ @Override
+ public final String toString() {
+ return "DEFAULT_AND_LOCALHOST";
+ }
+ };
+
+ /**
+ * The STRICT HostnameVerifier works the same way as java.net.URL in Sun
+ * Java 1.4, Sun Java 5, Sun Java 6. It's also pretty close to IE6.
+ * This implementation appears to be compliant with RFC 2818 for dealing
+ * with wildcards.
+ * <p/>
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts. The
+ * one divergence from IE6 is how we only check the first CN. IE6 allows
+ * a match against any of the CNs present. We decided to follow in
+ * Sun Java 1.4's footsteps and only check the first CN.
+ * <p/>
+ * A wildcard such as "*.foo.com" matches only subdomains in the same
+ * level, for example "a.foo.com". It does not match deeper subdomains
+ * such as "a.b.foo.com".
+ */
+ SSLHostnameVerifier STRICT =
+ new AbstractVerifier() {
+ @Override
+ public final void check(final String[] host, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ check(host, cns, subjectAlts, false, true);
+ }
+
+ @Override
+ public final String toString() {
+ return "STRICT";
+ }
+ };
+
+ /**
+ * The STRICT_IE6 HostnameVerifier works just like the STRICT one with one
+ * minor variation: the hostname can match against any of the CN's in the
+ * server's certificate, not just the first one. This behaviour is
+ * identical to IE6's behaviour.
+ */
+ SSLHostnameVerifier STRICT_IE6 =
+ new AbstractVerifier() {
+ @Override
+ public final void check(final String[] host, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ check(host, cns, subjectAlts, true, true);
+ }
+
+ @Override
+ public final String toString() {
+ return "STRICT_IE6";
+ }
+ };
+
+ /**
+ * The ALLOW_ALL HostnameVerifier essentially turns hostname verification
+ * off. This implementation is a no-op, and never throws the SSLException.
+ */
+ SSLHostnameVerifier ALLOW_ALL =
+ new AbstractVerifier() {
+ @Override
+ public final void check(final String[] host, final String[] cns,
+ final String[] subjectAlts) {
+ // Allow everything - so never blowup.
+ }
+
+ @Override
+ public final String toString() {
+ return "ALLOW_ALL";
+ }
+ };
+
+ abstract class AbstractVerifier implements SSLHostnameVerifier {
+
+ /**
+ * This contains a list of 2nd-level domains that aren't allowed to
+ * have wildcards when combined with country-codes.
+ * For example: [*.co.uk].
+ * <p/>
+ * The [*.co.uk] problem is an interesting one. Should we just hope
+ * that CA's would never foolishly allow such a certificate to happen?
+ * Looks like we're the only implementation guarding against this.
+ * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
+ */
+ private static final String[] BAD_COUNTRY_2LDS =
+ {"ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
+ "lg", "ne", "net", "or", "org"};
+
+ private static final String[] LOCALHOSTS = {"::1", "127.0.0.1",
+ "localhost",
+ "localhost.localdomain"};
+
+
+ static {
+ // Just in case developer forgot to manually sort the array. :-)
+ Arrays.sort(BAD_COUNTRY_2LDS);
+ Arrays.sort(LOCALHOSTS);
+ }
+
+ protected AbstractVerifier() {
+ }
+
+ /**
+ * The javax.net.ssl.HostnameVerifier contract.
+ *
+ * @param host 'hostname' we used to create our socket
+ * @param session SSLSession with the remote server
+ * @return true if the host matched the one in the certificate.
+ */
+ @Override
+ public boolean verify(String host, SSLSession session) {
+ try {
+ Certificate[] certs = session.getPeerCertificates();
+ X509Certificate x509 = (X509Certificate) certs[0];
+ check(new String[]{host}, x509);
+ return true;
+ } catch (SSLException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void check(String host, SSLSocket ssl) throws IOException {
+ check(new String[]{host}, ssl);
+ }
+
+ @Override
+ public void check(String host, X509Certificate cert)
+ throws SSLException {
+ check(new String[]{host}, cert);
+ }
+
+ @Override
+ public void check(String host, String[] cns, String[] subjectAlts)
+ throws SSLException {
+ check(new String[]{host}, cns, subjectAlts);
+ }
+
+ @Override
+ public void check(String[] host, SSLSocket ssl)
+ throws IOException {
+ if (host == null) {
+ throw new NullPointerException("host to verify is null");
+ }
+
+ SSLSession session = ssl.getSession();
+ if (session == null) {
+ // In our experience this only happens under IBM 1.4.x when
+ // spurious (unrelated) certificates show up in the server'
+ // chain. Hopefully this will unearth the real problem:
+ InputStream in = ssl.getInputStream();
+ in.available();
+ /*
+ If you're looking at the 2 lines of code above because
+ you're running into a problem, you probably have two
+ options:
+
+ #1. Clean up the certificate chain that your server
+ is presenting (e.g. edit "/etc/apache2/server.crt"
+ or wherever it is your server's certificate chain
+ is defined).
+
+ OR
+
+ #2. Upgrade to an IBM 1.5.x or greater JVM, or switch
+ to a non-IBM JVM.
+ */
+
+ // If ssl.getInputStream().available() didn't cause an
+ // exception, maybe at least now the session is available?
+ session = ssl.getSession();
+ if (session == null) {
+ // If it's still null, probably a startHandshake() will
+ // unearth the real problem.
+ ssl.startHandshake();
+
+ // Okay, if we still haven't managed to cause an exception,
+ // might as well go for the NPE. Or maybe we're okay now?
+ session = ssl.getSession();
+ }
+ }
+ Certificate[] certs;
+ try {
+ certs = session.getPeerCertificates();
+ } catch (SSLPeerUnverifiedException spue) {
+ InputStream in = ssl.getInputStream();
+ in.available();
+ // Didn't trigger anything interesting? Okay, just throw
+ // original.
+ throw spue;
+ }
+ X509Certificate x509 = (X509Certificate) certs[0];
+ check(host, x509);
+ }
+
+ @Override
+ public void check(String[] host, X509Certificate cert)
+ throws SSLException {
+ String[] cns = Certificates.getCNs(cert);
+ String[] subjectAlts = Certificates.getDNSSubjectAlts(cert);
+ check(host, cns, subjectAlts);
+ }
+
+ public void check(final String[] hosts, final String[] cns,
+ final String[] subjectAlts, final boolean ie6,
+ final boolean strictWithSubDomains)
+ throws SSLException {
+ // Build up lists of allowed hosts For logging/debugging purposes.
+ StringBuffer buf = new StringBuffer(32);
+ buf.append('<');
+ for (int i = 0; i < hosts.length; i++) {
+ String h = hosts[i];
+ h = h != null ? StringUtils.toLowerCase(h.trim()) : "";
+ hosts[i] = h;
+ if (i > 0) {
+ buf.append('/');
+ }
+ buf.append(h);
+ }
+ buf.append('>');
+ String hostnames = buf.toString();
+ // Build the list of names we're going to check. Our DEFAULT and
+ // STRICT implementations of the HostnameVerifier only use the
+ // first CN provided. All other CNs are ignored.
+ // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
+ final Set<String> names = new TreeSet<String>();
+ if (cns != null && cns.length > 0 && cns[0] != null) {
+ names.add(cns[0]);
+ if (ie6) {
+ for (int i = 1; i < cns.length; i++) {
+ names.add(cns[i]);
+ }
+ }
+ }
+ if (subjectAlts != null) {
+ for (int i = 0; i < subjectAlts.length; i++) {
+ if (subjectAlts[i] != null) {
+ names.add(subjectAlts[i]);
+ }
+ }
+ }
+ if (names.isEmpty()) {
+ String msg = "Certificate for " + hosts[0] + " doesn't contain CN or DNS subjectAlt";
+ throw new SSLException(msg);
+ }
+
+ // StringBuffer for building the error message.
+ buf = new StringBuffer();
+
+ boolean match = false;
+ out:
+ for (Iterator<String> it = names.iterator(); it.hasNext();) {
+ // Don't trim the CN, though!
+ final String cn = StringUtils.toLowerCase(it.next());
+ // Store CN in StringBuffer in case we need to report an error.
+ buf.append(" <");
+ buf.append(cn);
+ buf.append('>');
+ if (it.hasNext()) {
+ buf.append(" OR");
+ }
+
+ // The CN better have at least two dots if it wants wildcard
+ // action. It also can't be [*.co.uk] or [*.co.jp] or
+ // [*.org.uk], etc...
+ boolean doWildcard = cn.startsWith("*.")
+ && cn.lastIndexOf('.') >= 0
+ && !isIP4Address(cn)
+ && acceptableCountryWildcard(cn);
+
+ for (int i = 0; i < hosts.length; i++) {
+ final String hostName =
+ StringUtils.toLowerCase(hosts[i].trim());
+ if (doWildcard) {
+ match = hostName.endsWith(cn.substring(1));
+ if (match && strictWithSubDomains) {
+ // If we're in strict mode, then [*.foo.com] is not
+ // allowed to match [a.b.foo.com]
+ match = countDots(hostName) == countDots(cn);
+ }
+ } else {
+ match = hostName.equals(cn);
+ }
+ if (match) {
+ break out;
+ }
+ }
+ }
+ if (!match) {
+ throw new SSLException("hostname in certificate didn't match: " + hostnames + " !=" + buf);
+ }
+ }
+
+ public static boolean isIP4Address(final String cn) {
+ boolean isIP4 = true;
+ String tld = cn;
+ int x = cn.lastIndexOf('.');
+ // We only bother analyzing the characters after the final dot
+ // in the name.
+ if (x >= 0 && x + 1 < cn.length()) {
+ tld = cn.substring(x + 1);
+ }
+ for (int i = 0; i < tld.length(); i++) {
+ if (!Character.isDigit(tld.charAt(0))) {
+ isIP4 = false;
+ break;
+ }
+ }
+ return isIP4;
+ }
+
+ public static boolean acceptableCountryWildcard(final String cn) {
+ int cnLen = cn.length();
+ if (cnLen >= 7 && cnLen <= 9) {
+ // Look for the '.' in the 3rd-last position:
+ if (cn.charAt(cnLen - 3) == '.') {
+ // Trim off the [*.] and the [.XX].
+ String s = cn.substring(2, cnLen - 3);
+ // And test against the sorted array of bad 2lds:
+ int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
+ return x < 0;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isLocalhost(String host) {
+ host = host != null ? StringUtils.toLowerCase(host.trim()) : "";
+ if (host.startsWith("::1")) {
+ int x = host.lastIndexOf('%');
+ if (x >= 0) {
+ host = host.substring(0, x);
+ }
+ }
+ int x = Arrays.binarySearch(LOCALHOSTS, host);
+ return x >= 0;
+ }
+
+ /**
+ * Counts the number of dots "." in a string.
+ *
+ * @param s string to count dots from
+ * @return number of dots
+ */
+ public static int countDots(final String s) {
+ int count = 0;
+ for (int i = 0; i < s.length(); i++) {
+ if (s.charAt(i) == '.') {
+ count++;
+ }
+ }
+ return count;
+ }
+ }
+
+ class Certificates {
+ public static String[] getCNs(X509Certificate cert) {
+ final List<String> cnList = new LinkedList<String>();
+ /*
+ Sebastian Hauer's original StrictSSLProtocolSocketFactory used
+ getName() and had the following comment:
+
+ Parses a X.500 distinguished name for the value of the
+ "Common Name" field. This is done a bit sloppy right
+ now and should probably be done a bit more according to
+ <code>RFC 2253</code>.
+
+ I've noticed that toString() seems to do a better job than
+ getName() on these X500Principal objects, so I'm hoping that
+ addresses Sebastian's concern.
+
+ For example, getName() gives me this:
+ 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d
+
+ whereas toString() gives me this:
+ EMAILADDRESS=juliusdavies@cucbc.com
+
+ Looks like toString() even works with non-ascii domain names!
+ I tested it with "花子.co.jp" and it worked fine.
+ */
+ String subjectPrincipal = cert.getSubjectX500Principal().toString();
+ StringTokenizer st = new StringTokenizer(subjectPrincipal, ",");
+ while (st.hasMoreTokens()) {
+ String tok = st.nextToken();
+ int x = tok.indexOf("CN=");
+ if (x >= 0) {
+ cnList.add(tok.substring(x + 3));
+ }
+ }
+ if (!cnList.isEmpty()) {
+ String[] cns = new String[cnList.size()];
+ cnList.toArray(cns);
+ return cns;
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Extracts the array of SubjectAlt DNS names from an X509Certificate.
+ * Returns null if there aren't any.
+ * <p/>
+ * Note: Java doesn't appear able to extract international characters
+ * from the SubjectAlts. It can only extract international characters
+ * from the CN field.
+ * <p/>
+ * (Or maybe the version of OpenSSL I'm using to test isn't storing the
+ * international characters correctly in the SubjectAlts?).
+ *
+ * @param cert X509Certificate
+ * @return Array of SubjectALT DNS names stored in the certificate.
+ */
+ public static String[] getDNSSubjectAlts(X509Certificate cert) {
+ final List<String> subjectAltList = new LinkedList<String>();
+ Collection<List<?>> c = null;
+ try {
+ c = cert.getSubjectAlternativeNames();
+ } catch (CertificateParsingException cpe) {
+ // Should probably log.debug() this?
+ cpe.printStackTrace();
+ }
+ if (c != null) {
+ Iterator<List<?>> it = c.iterator();
+ while (it.hasNext()) {
+ List<?> list = it.next();
+ int type = ((Integer) list.get(0)).intValue();
+ // If type is 2, then we've got a dNSName
+ if (type == 2) {
+ String s = (String) list.get(1);
+ subjectAltList.add(s);
+ }
+ }
+ }
+ if (!subjectAltList.isEmpty()) {
+ String[] subjectAlts = new String[subjectAltList.size()];
+ subjectAltList.toArray(subjectAlts);
+ return subjectAlts;
+ } else {
+ return null;
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/ConnectionConfigurator.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/ConnectionConfigurator.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/ConnectionConfigurator.java
new file mode 100644
index 0000000..c913e59
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/ConnectionConfigurator.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.kerby.has.common.util;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * Interface to configure {@link HttpURLConnection} created by
+ * {@link org.apache.kerby.has.common.spnego.AuthenticatedURL} instances.
+ */
+public interface ConnectionConfigurator {
+
+ /**
+ * Configures the given {@link HttpURLConnection} instance.
+ *
+ * @param conn the {@link HttpURLConnection} instance to configure.
+ * @return the configured {@link HttpURLConnection} instance.
+ *
+ * @throws IOException if an IO error occurred.
+ */
+ HttpURLConnection configure(HttpURLConnection conn) throws IOException;
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
new file mode 100644
index 0000000..3ccd749
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/HasUtil.java
@@ -0,0 +1,81 @@
+/**
+ * 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.kerby.has.common.util;
+
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+
+public class HasUtil {
+
+ /**
+ * Get has configuration
+ * @param hasConfFile configuration directory
+ * @return has configuration
+ */
+ public static HasConfig getHasConfig(File hasConfFile) throws HasException {
+
+ if (hasConfFile.exists()) {
+ HasConfig hasConfig = new HasConfig();
+ try {
+ hasConfig.addIniConfig(hasConfFile);
+ } catch (IOException e) {
+ throw new HasException("Can not load the has configuration file "
+ + hasConfFile.getAbsolutePath());
+ }
+ return hasConfig;
+ }
+
+ return null;
+ }
+
+ public static void setEnableConf(File hasConfFile, String value)
+ throws HasException, IOException {
+ String oldValue = getHasConfig(hasConfFile).getEnableConf();
+ if (oldValue == null) {
+ throw new HasException("Please set enable_conf in has-server.conf.");
+ }
+ if (oldValue.equals(value)) {
+ return;
+ }
+ try {
+ BufferedReader bf = new BufferedReader(new FileReader(hasConfFile));
+ StringBuilder sb = new StringBuilder();
+ String tempString;
+ while ((tempString = bf.readLine()) != null) {
+ if (tempString.trim().startsWith("enable_conf")) {
+ tempString = tempString.replace(oldValue, value);
+ }
+ sb.append(tempString + "\n");
+ }
+ PrintStream ps = new PrintStream(new FileOutputStream(hasConfFile));
+ ps.print(sb.toString());
+ bf.close();
+ } catch (FileNotFoundException e) {
+ throw new HasException("Can not load the has configuration file "
+ + hasConfFile.getAbsolutePath());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/PlatformName.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/PlatformName.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/PlatformName.java
new file mode 100644
index 0000000..8a2c961
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/PlatformName.java
@@ -0,0 +1,59 @@
+/**
+ * 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.kerby.has.common.util;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * A helper class for getting build-info of the java-vm.
+ *
+ */
+@InterfaceAudience.LimitedPrivate({"HBase"})
+@InterfaceStability.Unstable
+public class PlatformName {
+ /**
+ * The complete platform 'name' to identify the platform as
+ * per the java-vm.
+ */
+ public static final String PLATFORM_NAME =
+ (System.getProperty("os.name").startsWith("Windows")
+ ? System.getenv("os") : System.getProperty("os.name"))
+ + "-" + System.getProperty("os.arch")
+ + "-" + System.getProperty("sun.arch.data.model");
+
+ /**
+ * The java vendor name used in this platform.
+ */
+ public static final String JAVA_VENDOR_NAME = System.getProperty("java.vendor");
+
+ /**
+ * A public static variable to indicate the current java vendor is
+ * IBM java or not.
+ */
+ public static final boolean IBM_JAVA = JAVA_VENDOR_NAME.contains("IBM");
+
+ public static void main(String[] args) {
+ System.out.println(PLATFORM_NAME);
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/StringUtils.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/StringUtils.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/StringUtils.java
new file mode 100644
index 0000000..b9c323d
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/StringUtils.java
@@ -0,0 +1,55 @@
+/**
+ * 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.kerby.has.common.util;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.util.Locale;
+
+/**
+ * General string utils
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class StringUtils {
+
+ /**
+ * Converts all of the characters in this String to lower case with
+ * Locale.ENGLISH.
+ *
+ * @param str string to be converted
+ * @return the str, converted to lowercase.
+ */
+ public static String toLowerCase(String str) {
+ return str.toLowerCase(Locale.ENGLISH);
+ }
+
+ /**
+ * Converts all of the characters in this String to upper case with
+ * Locale.ENGLISH.
+ *
+ * @param str string to be converted
+ * @return the str, converted to uppercase.
+ */
+ public static String toUpperCase(String str) {
+ return str.toUpperCase(Locale.ENGLISH);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java
----------------------------------------------------------------------
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java
new file mode 100644
index 0000000..06a8822
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java
@@ -0,0 +1,197 @@
+/**
+ * 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.kerby.has.common.util;
+
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.common.spnego.AuthenticatedURL;
+import org.apache.kerby.has.common.spnego.AuthenticationException;
+import org.apache.kerby.has.common.spnego.KerberosHasAuthenticator;
+import org.apache.kerby.has.common.ssl.SSLFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.GeneralSecurityException;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+/**
+ * Utilities for handling URLs
+ */
+@InterfaceStability.Unstable
+public class URLConnectionFactory {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(URLConnectionFactory.class);
+
+ /**
+ * Timeout for socket connects and reads
+ */
+ // 1 minute
+ public static final int DEFAULT_SOCKET_TIMEOUT = 60 * 1000;
+ private final ConnectionConfigurator connConfigurator;
+
+ private static final ConnectionConfigurator DEFAULT_TIMEOUT_CONN_CONFIGURATOR
+ = new ConnectionConfigurator() {
+ @Override
+ public HttpURLConnection configure(HttpURLConnection conn)
+ throws IOException {
+ URLConnectionFactory.setTimeouts(conn,
+ DEFAULT_SOCKET_TIMEOUT,
+ DEFAULT_SOCKET_TIMEOUT);
+ return conn;
+ }
+ };
+
+ /**
+ * The URLConnectionFactory that sets the default timeout and it only trusts
+ * Java's SSL certificates.
+ */
+ public static final URLConnectionFactory DEFAULT_SYSTEM_CONNECTION_FACTORY =
+ new URLConnectionFactory(DEFAULT_TIMEOUT_CONN_CONFIGURATOR);
+
+ /**
+ * Construct a new URLConnectionFactory based on the configuration. It will
+ * try to load SSL certificates when it is specified.
+ */
+ public static URLConnectionFactory newDefaultURLConnectionFactory(HasConfig conf) {
+ ConnectionConfigurator conn = null;
+ try {
+ conn = newSslConnConfigurator(DEFAULT_SOCKET_TIMEOUT, conf);
+ } catch (Exception e) {
+ LOG.debug(
+ "Cannot load customized ssl related configuration. Fallback to system-generic settings.",
+ e);
+ conn = DEFAULT_TIMEOUT_CONN_CONFIGURATOR;
+ }
+ return new URLConnectionFactory(conn);
+ }
+
+ URLConnectionFactory(ConnectionConfigurator connConfigurator) {
+ this.connConfigurator = connConfigurator;
+ }
+
+ /**
+ * Create a new ConnectionConfigurator for SSL connections
+ */
+ private static ConnectionConfigurator newSslConnConfigurator(
+ final int defaultTimeout, HasConfig conf)
+ throws IOException, GeneralSecurityException, HasException {
+ final SSLFactory factory;
+ final SSLSocketFactory sf;
+ final HostnameVerifier hv;
+ final int connectTimeout;
+ final int readTimeout;
+
+ factory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ factory.init();
+ sf = factory.createSSLSocketFactory();
+ hv = factory.getHostnameVerifier();
+
+ connectTimeout = defaultTimeout;
+
+ readTimeout = defaultTimeout;
+
+ return new ConnectionConfigurator() {
+ @Override
+ public HttpURLConnection configure(HttpURLConnection conn)
+ throws IOException {
+ if (conn instanceof HttpsURLConnection) {
+ HttpsURLConnection c = (HttpsURLConnection) conn;
+ c.setSSLSocketFactory(sf);
+ c.setHostnameVerifier(hv);
+ }
+ URLConnectionFactory.setTimeouts(conn, connectTimeout, readTimeout);
+ return conn;
+ }
+ };
+ }
+
+ /**
+ * Opens a url with read and connect timeouts
+ *
+ * @param url
+ * to open
+ * @return URLConnection
+ * @throws IOException
+ */
+ public URLConnection openConnection(URL url) throws IOException {
+ try {
+ return openConnection(url, false, null);
+ } catch (AuthenticationException e) {
+ // Unreachable
+ LOG.error("Open connection {} failed", url, e);
+ return null;
+ }
+ }
+
+ /**
+ * Opens a url with read and connect timeouts
+ *
+ * @param url
+ * URL to open
+ * @param isSpnego
+ * whether the url should be authenticated via SPNEGO
+ * @return URLConnection
+ * @throws IOException
+ * @throws AuthenticationException
+ */
+ public URLConnection openConnection(URL url, boolean isSpnego, HasConfig hasConfig)
+ throws IOException, AuthenticationException {
+ if (isSpnego && hasConfig != null) {
+ LOG.debug("open AuthenticatedURL connection {}", url);
+// UserGroupInformation.getCurrentUser().checkTGTAndReloginFromKeytab();
+ final AuthenticatedURL.Token authToken = new AuthenticatedURL.Token();
+ return new AuthenticatedURL(new KerberosHasAuthenticator(hasConfig.getAdminKeytab(),
+ hasConfig.getAdminKeytabPrincipal()),
+ connConfigurator).openConnection(url, authToken);
+ } else {
+ LOG.debug("open URL connection");
+ URLConnection connection = url.openConnection();
+ if (connection instanceof HttpURLConnection) {
+ connConfigurator.configure((HttpURLConnection) connection);
+ }
+ return connection;
+ }
+ }
+
+ /**
+ * Sets timeout parameters on the given URLConnection.
+ *
+ * @param connection
+ * URLConnection to set
+ * @param connectTimeout
+ * the connection and read timeout of the connection.
+ */
+ private static void setTimeouts(URLConnection connection,
+ int connectTimeout,
+ int readTimeout) {
+ connection.setConnectTimeout(connectTimeout);
+ connection.setReadTimeout(readTimeout);
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/pom.xml
----------------------------------------------------------------------
diff --git a/has-project/has-server/pom.xml b/has-project/has-server/pom.xml
new file mode 100644
index 0000000..9338981
--- /dev/null
+++ b/has-project/has-server/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+
+ <parent>
+ <artifactId>has-project</artifactId>
+ <groupId>org.apache.kerby</groupId>
+ <version>1.1.1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>has-server</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerby-config</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-identity</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-server-api-all</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerby-kdc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>json-backend</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>token-provider</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ <version>${hadoop.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>has-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServer.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServer.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServer.java
new file mode 100644
index 0000000..ed65103
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/HasServer.java
@@ -0,0 +1,323 @@
+/**
+ * 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.kerby.has.server;
+
+import org.apache.hadoop.http.HttpConfig;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.common.util.HasUtil;
+import org.apache.kerby.has.server.web.WebConfigKey;
+import org.apache.kerby.has.server.web.WebServer;
+import org.apache.kerby.kerberos.kdc.impl.NettyKdcServerImpl;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadmin;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadminImpl;
+import org.apache.kerby.kerberos.kerb.client.ClientUtil;
+import org.apache.kerby.kerberos.kerb.client.KrbConfig;
+import org.apache.kerby.kerberos.kerb.client.KrbSetting;
+import org.apache.kerby.kerberos.kerb.identity.backend.IdentityBackend;
+import org.apache.kerby.kerberos.kerb.server.KdcServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * The HAS KDC server implementation.
+ */
+public class HasServer {
+ public static final Logger LOG = LoggerFactory.getLogger(HasServer.class);
+
+ private static HasServer server = null;
+
+ private KrbSetting krbSetting;
+ private KdcServer kdcServer;
+ private WebServer webServer;
+ private File confDir;
+ private File workDir;
+ private String kdcHost;
+ private HasConfig hasConfig;
+
+ public HasServer(File confDir) throws KrbException {
+ this.confDir = confDir;
+ }
+
+ private void setConfDir(File confDir) {
+ this.confDir = confDir;
+ }
+
+ public File getConfDir() {
+ return confDir;
+ }
+
+ public File getWorkDir() {
+ return workDir;
+ }
+
+ public void setWorkDir(File workDir) {
+ this.workDir = workDir;
+ }
+
+ public void setKdcHost(String host) {
+ this.kdcHost = host;
+ }
+
+ public String getKdcHost() {
+ return kdcHost;
+ }
+
+ public KrbSetting getKrbSetting() {
+ return krbSetting;
+ }
+
+ public KdcServer getKdcServer() {
+ return kdcServer;
+ }
+
+ public WebServer getWebServer() {
+ return webServer;
+ }
+
+ public void setWebServer(WebServer webServer) {
+ this.webServer = webServer;
+ }
+
+ public void startKdcServer() throws HasException {
+ try {
+ kdcServer = new KdcServer(confDir);
+ } catch (KrbException e) {
+ throw new HasException("Failed to create KdcServer. " + e);
+ }
+ kdcServer.setWorkDir(workDir);
+ kdcServer.setInnerKdcImpl(new NettyKdcServerImpl(kdcServer.getKdcSetting()));
+ try {
+ kdcServer.init();
+ } catch (KrbException e) {
+ LOG.error("Errors occurred when init has kdc server: " + e.getMessage());
+ throw new HasException("Errors occurred when init has kdc server: " + e.getMessage());
+ }
+
+ KrbConfig krbConfig = null;
+ try {
+ krbConfig = ClientUtil.getConfig(confDir);
+ } catch (KrbException e) {
+ new HasException("Errors occurred when getting the config from conf dir. "
+ + e.getMessage());
+ }
+ if (krbConfig == null) {
+ krbConfig = new KrbConfig();
+ }
+ this.krbSetting = new KrbSetting(krbConfig);
+ try {
+ kdcServer.start();
+ } catch (KrbException e) {
+ throw new HasException("Failed to start kdc server. " + e);
+ }
+ try {
+ HasUtil.setEnableConf(new File(confDir, "has-server.conf"), "false");
+ } catch (Exception e) {
+ throw new HasException("Failed to enable conf. " + e);
+ }
+ setHttpFilter();
+ }
+
+ private void setHttpFilter() throws HasException {
+ File httpKeytabFile = new File(workDir, "http.keytab");
+ LocalKadmin kadmin = new LocalKadminImpl(kdcServer.getKdcSetting(),
+ kdcServer.getIdentityService());
+ createHttpPrincipal(kadmin);
+ try {
+ kadmin.exportKeytab(httpKeytabFile, getHttpPrincipal());
+ } catch (KrbException e) {
+ throw new HasException("Failed to export keytab: " + e.getMessage());
+ }
+ webServer.getConf().setString(WebConfigKey.HAS_AUTHENTICATION_FILTER_AUTH_TYPE,
+ hasConfig.getFilterAuthType());
+ webServer.getConf().setString(WebConfigKey.HAS_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY,
+ getHttpPrincipal());
+ webServer.getConf().setString(WebConfigKey.HAS_AUTHENTICATION_KERBEROS_KEYTAB_KEY,
+ httpKeytabFile.getPath());
+ webServer.defineFilter();
+ }
+
+ public void createHttpPrincipal(LocalKadmin kadmin) throws HasException {
+ String httpPrincipal = getHttpPrincipal();
+ IdentityBackend backend = kdcServer.getIdentityService();
+ try {
+ if (backend.getIdentity(httpPrincipal) == null) {
+ kadmin.addPrincipal(httpPrincipal);
+ } else {
+ LOG.info("The http principal already exists in backend.");
+ }
+ } catch (KrbException e) {
+ throw new HasException("Failed to add principal, " + e.getMessage());
+ }
+ }
+
+ public String getHttpPrincipal() throws HasException {
+ String realm = kdcServer.getKdcSetting().getKdcRealm();
+ String nameString;
+ try {
+ InetAddress addr = InetAddress.getLocalHost();
+ String fqName = addr.getCanonicalHostName();
+ nameString = "HTTP/" + fqName + "@" + realm;
+ } catch (UnknownHostException e) {
+ throw new HasException(e);
+ }
+ LOG.info("The http principal name is: " + nameString);
+ return nameString;
+ }
+
+ public void stopKdcServer() {
+ try {
+ kdcServer.stop();
+ } catch (KrbException e) {
+ LOG.error("Fail to stop has kdc server");
+ }
+ }
+
+ public void startWebServer() throws HasException {
+ if (webServer == null) {
+ HasConfig conf = new HasConfig();
+
+ // Parse has-server.conf to get http_host and http_port
+ File confFile = new File(confDir, "has-server.conf");
+ hasConfig = HasUtil.getHasConfig(confFile);
+ if (hasConfig != null) {
+ try {
+ String httpHost;
+ String httpPort;
+ String httpsHost;
+ String httpsPort;
+ if (hasConfig.getHttpHost() != null) {
+ httpHost = hasConfig.getHttpHost();
+ } else {
+ LOG.info("Cannot get the http_host from has-server.conf, using the default http host.");
+ httpHost = WebConfigKey.HAS_HTTP_HOST_DEFAULT;
+ }
+ if (hasConfig.getHttpPort() != null) {
+ httpPort = hasConfig.getHttpPort();
+ } else {
+ LOG.info("Cannot get the http_port from has-server.conf, using the default http port.");
+ httpPort = String.valueOf(WebConfigKey.HAS_HTTP_PORT_DEFAULT);
+ }
+ if (hasConfig.getHttpsHost() != null) {
+ httpsHost = hasConfig.getHttpsHost();
+ } else {
+ LOG.info("Cannot get the https_host from has-server.conf, using the default https host.");
+ httpsHost = WebConfigKey.HAS_HTTPS_HOST_DEFAULT;
+ }
+ if (hasConfig.getHttpsPort() != null) {
+ httpsPort = hasConfig.getHttpsPort();
+ } else {
+ LOG.info("Cannot get the https_port from has-server.conf , using the default https port.");
+ httpsPort = String.valueOf(WebConfigKey.HAS_HTTPS_PORT_DEFAULT);
+ }
+ String hasHttpAddress = httpHost + ":" + httpPort;
+ String hasHttpsAddress = httpsHost + ":" + httpsPort;
+ LOG.info("The web server http address: " + hasHttpAddress);
+ LOG.info("The web server https address: " + hasHttpsAddress);
+
+ conf.setString(WebConfigKey.HAS_HTTP_ADDRESS_KEY, hasHttpAddress);
+ conf.setString(WebConfigKey.HAS_HTTPS_ADDRESS_KEY, hasHttpsAddress);
+ conf.setString(WebConfigKey.HAS_HTTP_POLICY_KEY,
+ HttpConfig.Policy.HTTP_AND_HTTPS.name());
+ conf.setString(WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY,
+ hasConfig.getSslServerConf());
+ webServer = new WebServer(conf);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("https_port should be a number. "
+ + e.getMessage());
+ }
+ } else {
+ throw new HasException("has-server.conf not found in " + confDir + ". ");
+ }
+ } else {
+ hasConfig = webServer.getConf();
+ }
+ webServer.start();
+ webServer.defineConfFilter();
+ try {
+ HasUtil.setEnableConf(new File(confDir, "has-server.conf"), "true");
+ } catch (IOException e) {
+ throw new HasException("Errors occurred when enable conf. " + e.getMessage());
+ }
+ webServer.setWebServerAttribute(this);
+ }
+
+ public void stopWebServer() {
+ if (webServer != null) {
+ try {
+ webServer.stop();
+ } catch (Exception e) {
+ LOG.error("Failed to stop http server. " + e.getMessage());
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ if (args[0].equals("-start")) {
+ String confDirPath = args[1];
+ String workDirPath = args[2];
+ File confDir = new File(confDirPath);
+ File workDir = new File(workDirPath);
+ if (!confDir.exists() || !workDir.exists()) {
+ LOG.error("Invalid or not exist conf-dir or work-dir");
+ System.exit(3);
+ }
+ try {
+ server = new HasServer(confDir);
+ } catch (KrbException e) {
+ LOG.error("Errors occurred when create kdc server: " + e.getMessage());
+ System.exit(4);
+ }
+ server.setConfDir(confDir);
+ server.setWorkDir(workDir);
+ //Only start the webserver, the kdcserver could be started after setting the realm
+ try {
+ server.startWebServer();
+ } catch (HasException e) {
+ LOG.error("Errors occurred when start has http server: " + e.getMessage());
+ System.exit(6);
+ }
+
+ if (server.getWebServer().getHttpAddress() != null) {
+ LOG.info("HAS http server started.");
+ LOG.info("host: " + server.getWebServer().getHttpAddress().getHostName());
+ LOG.info("port: " + server.getWebServer().getHttpAddress().getPort());
+ }
+ if (server.getWebServer().getHttpsAddress() != null) {
+ LOG.info("HAS https server started.");
+ LOG.info("host: " + server.getWebServer().getHttpsAddress().getHostName());
+ LOG.info("port: " + server.getWebServer().getHttpsAddress().getPort());
+ }
+ } else if (args[0].equals("-stop")) {
+ if (server != null) {
+ server.stopWebServer();
+ server.stopKdcServer();
+ }
+ } else {
+ System.exit(2);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/ConfFilter.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/ConfFilter.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/ConfFilter.java
new file mode 100644
index 0000000..04c3537
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/ConfFilter.java
@@ -0,0 +1,55 @@
+package org.apache.kerby.has.server.web;
+
+
+import org.apache.hadoop.classification.InterfaceAudience.Private;
+import org.apache.hadoop.classification.InterfaceStability.Unstable;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.common.util.HasUtil;
+import org.apache.kerby.has.server.HasServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 java.io.File;
+import java.io.IOException;
+
+@Private
+@Unstable
+public class ConfFilter implements Filter {
+ public static final Logger LOG = LoggerFactory.getLogger(ConfFilter.class);
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+ FilterChain filterChain) throws IOException, ServletException {
+
+ final HasServer hasServer = WebServer.getHasServerFromContext(
+ servletRequest.getServletContext());
+ HasConfig hasConfig;
+ try {
+ hasConfig = HasUtil.getHasConfig(
+ new File(hasServer.getConfDir(), "has-server.conf"));
+ String isEnableConf = hasConfig.getEnableConf();
+ if (!isEnableConf.equals("true")) {
+ throw new RuntimeException("The kdc has started.");
+ }
+ filterChain.doFilter(servletRequest, servletResponse);
+ } catch (HasException e) {
+ LOG.error(e.getMessage());
+ }
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebConfigKey.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebConfigKey.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebConfigKey.java
new file mode 100644
index 0000000..ff31229
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebConfigKey.java
@@ -0,0 +1,62 @@
+/**
+ * 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.kerby.has.server.web;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.http.HttpConfig;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+
+/**
+ * This class contains constants for configuration keys and default values
+ * used in hdfs.
+ */
+@InterfaceAudience.Private
+public class WebConfigKey {
+
+ public static final int HAS_HTTP_PORT_DEFAULT = 9870;
+ public static final String HAS_HTTP_HOST_DEFAULT = "0.0.0.0";
+ public static final String HAS_HTTP_ADDRESS_KEY = "has.http-address";
+ public static final String HAS_HTTP_ADDRESS_DEFAULT = HAS_HTTP_HOST_DEFAULT + ":" + HAS_HTTP_PORT_DEFAULT;
+
+ public static final String HAS_HTTPS_BIND_HOST_KEY = "has.https-bind-host";
+ public static final int HAS_HTTPS_PORT_DEFAULT = 9871;
+ public static final String HAS_HTTPS_HOST_DEFAULT = "0.0.0.0";
+ public static final String HAS_HTTPS_ADDRESS_KEY = "has.https-address";
+ public static final String HAS_HTTPS_ADDRESS_DEFAULT = HAS_HTTPS_HOST_DEFAULT + ":" + HAS_HTTPS_PORT_DEFAULT;
+ public static final String HAS_HTTP_POLICY_KEY = "has.http.policy";
+ public static final String HAS_HTTP_POLICY_DEFAULT = HttpConfig.Policy.HTTPS_ONLY.name();
+
+ public static final String HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY = "has.https.server.keystore.resource";
+ public static final String HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_DEFAULT = "ssl-server.xml";
+ public static final String HAS_SERVER_HTTPS_KEYPASSWORD_KEY = "ssl.server.keystore.keypassword";
+ public static final String HAS_SERVER_HTTPS_KEYSTORE_PASSWORD_KEY = "ssl.server.keystore.password";
+ public static final String HAS_SERVER_HTTPS_KEYSTORE_LOCATION_KEY = "ssl.server.keystore.location";
+ public static final String HAS_SERVER_HTTPS_TRUSTSTORE_LOCATION_KEY = "ssl.server.truststore.location";
+ public static final String HAS_SERVER_HTTPS_TRUSTSTORE_PASSWORD_KEY = "ssl.server.truststore.password";
+ public static final String HAS_CLIENT_HTTPS_NEED_AUTH_KEY = "has.client.https.need-auth";
+ public static final boolean HAS_CLIENT_HTTPS_NEED_AUTH_DEFAULT = false;
+
+ public static final String HAS_AUTHENTICATION_FILTER_KEY = "has.web.authentication.filter";
+ public static final String HAS_AUTHENTICATION_FILTER_DEFAULT = AuthenticationFilter.class.getName();
+
+ public static final String HAS_AUTHENTICATION_FILTER_AUTH_TYPE = "has.authentication.filter.auth.type";
+ public static final String HAS_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY = "has.authentication.kerberos.principal";
+ public static final String HAS_AUTHENTICATION_KERBEROS_KEYTAB_KEY = "has.authentication.kerberos.keytab";
+ public static final String HAS_AUTHENTICATION_KERBEROS_NAME_RULES = "has.authentication.kerberos.name.rules";
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/59a31012/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebServer.java
----------------------------------------------------------------------
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebServer.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebServer.java
new file mode 100644
index 0000000..15e817c
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/WebServer.java
@@ -0,0 +1,335 @@
+/**
+ * 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.kerby.has.server.web;
+
+import org.apache.hadoop.HadoopIllegalArgumentException;
+import org.apache.hadoop.http.HttpConfig;
+import org.apache.hadoop.http.HttpServer2;
+import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.security.SecurityUtil;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.server.HasServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WebServer {
+ public static final Logger LOG = LoggerFactory.getLogger(WebServer.class);
+
+ private HttpServer2 httpServer;
+ private final HasConfig conf;
+
+ private InetSocketAddress httpAddress;
+ private InetSocketAddress httpsAddress;
+
+ protected static final String HAS_SERVER_ATTRIBUTE_KEY = "hasserver";
+
+ public WebServer(HasConfig conf) {
+ this.conf = conf;
+ }
+
+ public HasConfig getConf() {
+ return conf;
+ }
+
+ public void defineFilter() {
+ String authType = conf.getString(WebConfigKey.HAS_AUTHENTICATION_FILTER_AUTH_TYPE);
+ if (authType.equals("kerberos")) {
+ // add authentication filter for webhdfs
+ final String className = conf.getString(
+ WebConfigKey.HAS_AUTHENTICATION_FILTER_KEY,
+ WebConfigKey.HAS_AUTHENTICATION_FILTER_DEFAULT);
+
+ final String name = className;
+
+ Map<String, String> params = getAuthFilterParams(conf);
+
+ String adminPathSpec = "/has/v1/admin/*";
+ HttpServer2.defineFilter(httpServer.getWebAppContext(), name, className,
+ params, new String[]{adminPathSpec});
+ HttpServer2.LOG.info("Added filter '" + name + "' (class=" + className
+ + ")");
+ }
+ }
+
+ public void defineConfFilter() {
+ String confFilterName = ConfFilter.class.getName();
+ String confPath = "/has/v1/conf/*";
+ HttpServer2.defineFilter(httpServer.getWebAppContext(), confFilterName, confFilterName,
+ getAuthFilterParams(conf), new String[]{confPath});
+ HttpServer2.LOG.info("Added filter '" + confFilterName + "' (class=" + confFilterName
+ + ")");
+ }
+
+ private Map<String, String> getAuthFilterParams(HasConfig conf) {
+ Map<String, String> params = new HashMap<String, String>();
+
+ String authType = conf.getString(WebConfigKey.HAS_AUTHENTICATION_FILTER_AUTH_TYPE);
+ if (authType != null && !authType.isEmpty()) {
+ params.put(AuthenticationFilter.AUTH_TYPE, authType);
+ }
+ String principal = conf.getString(WebConfigKey.HAS_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY);
+ if (principal != null && !principal.isEmpty()) {
+ try {
+ principal = SecurityUtil.getServerPrincipal(principal,
+ getHttpsAddress().getHostName());
+ } catch (IOException e) {
+ LOG.warn("Errors occurred when get server principal. " + e.getMessage());
+ }
+ params.put(KerberosAuthenticationHandler.PRINCIPAL, principal);
+ }
+ String keytab = conf.getString(WebConfigKey.HAS_AUTHENTICATION_KERBEROS_KEYTAB_KEY);
+ if (keytab != null && !keytab.isEmpty()) {
+ params.put(KerberosAuthenticationHandler.KEYTAB, keytab);
+ }
+ String rule = conf.getString(WebConfigKey.HAS_AUTHENTICATION_KERBEROS_NAME_RULES);
+ if (rule != null && !rule.isEmpty()) {
+ params.put(KerberosAuthenticationHandler.NAME_RULES, rule);
+ } else {
+ params.put(KerberosAuthenticationHandler.NAME_RULES, "DEFAULT");
+ }
+ return params;
+ }
+
+ public InetSocketAddress getBindAddress() {
+ if (httpAddress != null) {
+ return httpAddress;
+ } else if (httpsAddress != null) {
+ return httpsAddress;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * for information related to the different configuration options and
+ * Http Policy is decided.
+ */
+ public void start() throws HasException {
+
+ HttpConfig.Policy policy = getHttpPolicy(conf);
+
+ final String bindHost =
+ conf.getString(WebConfigKey.HAS_HTTPS_BIND_HOST_KEY);
+ InetSocketAddress httpAddr = null;
+ if (policy.isHttpEnabled()) {
+ final String httpAddrString = conf.getString(
+ WebConfigKey.HAS_HTTP_ADDRESS_KEY,
+ WebConfigKey.HAS_HTTP_ADDRESS_DEFAULT);
+ httpAddr = NetUtils.createSocketAddr(httpAddrString);
+ if (bindHost != null && !bindHost.isEmpty()) {
+ httpAddr = new InetSocketAddress(bindHost, httpAddr.getPort());
+ }
+ LOG.info("Get the http address: " + httpAddr);
+ }
+
+ InetSocketAddress httpsAddr = null;
+ if (policy.isHttpsEnabled()) {
+ final String httpsAddrString = conf.getString(
+ WebConfigKey.HAS_HTTPS_ADDRESS_KEY,
+ WebConfigKey.HAS_HTTPS_ADDRESS_DEFAULT);
+ httpsAddr = NetUtils.createSocketAddr(httpsAddrString);
+
+ if (bindHost != null && !bindHost.isEmpty()) {
+ httpsAddr = new InetSocketAddress(bindHost, httpsAddr.getPort());
+ }
+ LOG.info("Get the https address: " + httpsAddr);
+ }
+
+ HttpServer2.Builder builder = httpServerTemplateForHAS(conf, httpAddr, httpsAddr, "has");
+
+ try {
+ httpServer = builder.build();
+ } catch (IOException e) {
+ throw new HasException("Errors occurred when building http server. " + e.getMessage());
+ }
+
+ try {
+ httpServer.start();
+ } catch (IOException e) {
+ throw new HasException("Errors occurred when starting http server. " + e.getMessage());
+ }
+ int connIdx = 0;
+ if (policy.isHttpEnabled()) {
+ httpAddress = httpServer.getConnectorAddress(connIdx++);
+ conf.setString(WebConfigKey.HAS_HTTP_ADDRESS_KEY,
+ NetUtils.getHostPortString(httpAddress));
+ }
+
+ if (policy.isHttpsEnabled()) {
+ httpsAddress = httpServer.getConnectorAddress(connIdx);
+ conf.setString(WebConfigKey.HAS_HTTPS_ADDRESS_KEY,
+ NetUtils.getHostPortString(httpsAddress));
+ }
+ }
+
+ public void setWebServerAttribute(HasServer hasServer) {
+ httpServer.setAttribute(HAS_SERVER_ATTRIBUTE_KEY, hasServer);
+ }
+
+ public static HasServer getHasServerFromContext(ServletContext context) {
+ return (HasServer) context.getAttribute(HAS_SERVER_ATTRIBUTE_KEY);
+ }
+
+ /**
+ * Get http policy.
+ */
+ public HttpConfig.Policy getHttpPolicy(HasConfig conf) {
+ String policyStr = conf.getString(WebConfigKey.HAS_HTTP_POLICY_KEY,
+ WebConfigKey.HAS_HTTP_POLICY_DEFAULT);
+ HttpConfig.Policy policy = HttpConfig.Policy.fromString(policyStr);
+ if (policy == null) {
+ throw new HadoopIllegalArgumentException("Unrecognized value '"
+ + policyStr + "' for " + WebConfigKey.HAS_HTTP_POLICY_KEY);
+ }
+
+ conf.setString(WebConfigKey.HAS_HTTP_POLICY_KEY, policy.name());
+ return policy;
+ }
+
+ /**
+ * Return a HttpServer.Builder that the ssm can use to
+ * initialize their HTTP / HTTPS server.
+ */
+ public HttpServer2.Builder httpServerTemplateForHAS(
+ HasConfig conf, final InetSocketAddress httpAddr, final InetSocketAddress httpsAddr,
+ String name) throws HasException {
+ HttpConfig.Policy policy = getHttpPolicy(conf);
+
+ HttpServer2.Builder builder = new HttpServer2.Builder().setName(name);
+
+ if (policy.isHttpEnabled()) {
+ if (httpAddr.getPort() == 0) {
+ builder.setFindPort(true);
+ }
+
+ URI uri = URI.create("http://" + NetUtils.getHostPortString(httpAddr));
+ builder.addEndpoint(uri);
+ LOG.info("Starting Web-server for " + name + " at: " + uri);
+ }
+
+ if (policy.isHttpsEnabled() && httpsAddr != null) {
+ HasConfig sslConf = loadSslConfiguration(conf);
+ loadSslConfToHttpServerBuilder(builder, sslConf);
+
+ if (httpsAddr.getPort() == 0) {
+ builder.setFindPort(true);
+ }
+
+ URI uri = URI.create("https://" + NetUtils.getHostPortString(httpsAddr));
+ builder.addEndpoint(uri);
+ LOG.info("Starting Web-server for " + name + " at: " + uri);
+ }
+
+ return builder;
+ }
+
+ /**
+ * Load HTTPS-related configuration.
+ */
+ public HasConfig loadSslConfiguration(HasConfig conf) throws HasException {
+ HasConfig sslConf = new HasConfig();
+
+ String sslConfigString = conf.getString(
+ WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY,
+ WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_DEFAULT);
+ LOG.info("Get the ssl config file: " + sslConfigString);
+ try {
+ sslConf.addIniConfig(new File(sslConfigString));
+ } catch (IOException e) {
+ throw new HasException("Errors occurred when adding config. " + e.getMessage());
+ }
+
+ final String[] reqSslProps = {
+ WebConfigKey.HAS_SERVER_HTTPS_TRUSTSTORE_LOCATION_KEY,
+ WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_LOCATION_KEY,
+ WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_PASSWORD_KEY,
+ WebConfigKey.HAS_SERVER_HTTPS_KEYPASSWORD_KEY
+ };
+
+ // Check if the required properties are included
+ for (String sslProp : reqSslProps) {
+ if (sslConf.getString(sslProp) == null) {
+ LOG.warn("SSL config " + sslProp + " is missing. If "
+ + WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY
+ + " is specified, make sure it is a relative path");
+ }
+ }
+
+ boolean requireClientAuth = conf.getBoolean(WebConfigKey.HAS_CLIENT_HTTPS_NEED_AUTH_KEY,
+ WebConfigKey.HAS_CLIENT_HTTPS_NEED_AUTH_DEFAULT);
+ sslConf.setBoolean(WebConfigKey.HAS_CLIENT_HTTPS_NEED_AUTH_KEY, requireClientAuth);
+ return sslConf;
+ }
+
+ public HttpServer2.Builder loadSslConfToHttpServerBuilder(HttpServer2.Builder builder,
+ HasConfig sslConf) {
+ return builder
+ .needsClientAuth(
+ sslConf.getBoolean(WebConfigKey.HAS_CLIENT_HTTPS_NEED_AUTH_KEY,
+ WebConfigKey.HAS_CLIENT_HTTPS_NEED_AUTH_DEFAULT))
+ .keyPassword(getPassword(sslConf, WebConfigKey.HAS_SERVER_HTTPS_KEYPASSWORD_KEY))
+ .keyStore(sslConf.getString("ssl.server.keystore.location"),
+ getPassword(sslConf, WebConfigKey.HAS_SERVER_HTTPS_KEYSTORE_PASSWORD_KEY),
+ sslConf.getString("ssl.server.keystore.type", "jks"))
+ .trustStore(sslConf.getString("ssl.server.truststore.location"),
+ getPassword(sslConf, WebConfigKey.HAS_SERVER_HTTPS_TRUSTSTORE_PASSWORD_KEY),
+ sslConf.getString("ssl.server.truststore.type", "jks"))
+ .excludeCiphers(
+ sslConf.getString("ssl.server.exclude.cipher.list"));
+ }
+
+ /**
+ * Leverages the Configuration.getPassword method to attempt to get
+ * passwords from the CredentialProvider API before falling back to
+ * clear text in config - if falling back is allowed.
+ *
+ * @param conf Configuration instance
+ * @param alias name of the credential to retreive
+ * @return String credential value or null
+ */
+ public String getPassword(HasConfig conf, String alias) {
+
+ return conf.getString(alias);
+ }
+
+ public void stop() throws Exception {
+ if (httpServer != null) {
+ httpServer.stop();
+ }
+ }
+
+ public InetSocketAddress getHttpAddress() {
+ return httpAddress;
+ }
+
+ public InetSocketAddress getHttpsAddress() {
+ return httpsAddress;
+ }
+}