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/11/15 05:12:13 UTC
[08/10] directory-kerby git commit: Add the HAS project to Kerby.
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/be580566/has/has-client/src/main/java/org/apache/hadoop/has/client/HasLoginModule.java
----------------------------------------------------------------------
diff --git a/has/has-client/src/main/java/org/apache/hadoop/has/client/HasLoginModule.java b/has/has-client/src/main/java/org/apache/hadoop/has/client/HasLoginModule.java
new file mode 100644
index 0000000..6c71236
--- /dev/null
+++ b/has/has-client/src/main/java/org/apache/hadoop/has/client/HasLoginModule.java
@@ -0,0 +1,491 @@
+/**
+ * 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.hadoop.has.client;
+
+import com.sun.security.auth.module.Krb5LoginModule;
+import org.apache.hadoop.has.common.HasException;
+import org.apache.kerby.kerberos.kerb.ccache.Credential;
+import org.apache.kerby.kerberos.kerb.type.ticket.TgtTicket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.security.jgss.krb5.Krb5Util;
+import sun.security.krb5.Credentials;
+import sun.security.krb5.KrbException;
+import sun.security.krb5.PrincipalName;
+
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.io.IOException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Login with tgt ticket
+ * The client's TGT will be retrieved from the API of HasClient
+ */
+//CHECKSTYLE.OFF
+public class HasLoginModule implements LoginModule {
+
+ public static final Logger LOG = LoggerFactory.getLogger(HasLoginModule.class);
+
+ Krb5LoginModule krb5LoginModule;
+
+ // initial state
+ private Subject subject;
+ private CallbackHandler callbackHandler;
+ private Map<String, Object> sharedState;
+ private Map<String, ?> options;
+
+ // configurable option
+ private boolean debug = false;
+ private boolean doNotPrompt = false;
+ private boolean useTgtTicket = false;
+ private String hadoopSecurityHas = null;
+ private String princName = null;
+
+ private boolean refreshKrb5Config = false;
+
+ // specify if initiator.
+ // perform authentication exchange if initiator
+ private boolean isInitiator = true;
+
+ // the authentication status
+ private boolean succeeded = false;
+ private boolean commitSucceeded = false;
+
+ private Credentials cred = null;
+
+ private PrincipalName principal = null;
+ private KerberosPrincipal kerbClientPrinc = null;
+ private KerberosTicket kerbTicket = null;
+ private StringBuffer krb5PrincName = null;
+ private boolean unboundServer = false;
+
+ /**
+ * Initialize this <code>LoginModule</code>.
+ * <p>
+ * <p>
+ *
+ * @param subject the <code>Subject</code> to be authenticated. <p>
+ * @param callbackHandler a <code>CallbackHandler</code> for
+ * communication with the end user (prompting for
+ * usernames and passwords, for example). <p>
+ * @param sharedState shared <code>LoginModule</code> state. <p>
+ * @param options options specified in the login
+ * <code>Configuration</code> for this particular
+ * <code>LoginModule</code>.
+ */
+ public void initialize(Subject subject,
+ CallbackHandler callbackHandler,
+ Map<String, ?> sharedState,
+ Map<String, ?> options) {
+
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ this.sharedState = (Map<String, Object>) sharedState;
+ this.options = options;
+
+ // initialize any configured options
+ useTgtTicket = "true".equalsIgnoreCase((String) options.get("useTgtTicket"));
+
+ if (useTgtTicket) {
+ debug = "true".equalsIgnoreCase((String) options.get("debug"));
+ doNotPrompt = "true".equalsIgnoreCase((String) options.get("doNotPrompt"));
+ useTgtTicket = "true".equalsIgnoreCase((String) options.get("useTgtTicket"));
+ hadoopSecurityHas = (String) options.get("hadoopSecurityHas");
+ princName = (String) options.get("principal");
+ refreshKrb5Config =
+ "true".equalsIgnoreCase((String) options.get("refreshKrb5Config"));
+
+ // check isInitiator value
+ String isInitiatorValue = ((String) options.get("isInitiator"));
+ if (isInitiatorValue != null) {
+ // use default, if value not set
+ isInitiator = "true".equalsIgnoreCase(isInitiatorValue);
+ }
+
+ if (debug) {
+ System.out.print("Debug is " + debug
+ + " doNotPrompt " + doNotPrompt
+ + " isInitiator " + isInitiator
+ + " refreshKrb5Config is " + refreshKrb5Config
+ + " principal is " + princName + "\n");
+ }
+ } else {
+ krb5LoginModule = new Krb5LoginModule();
+ krb5LoginModule.initialize(subject, callbackHandler, sharedState, options);
+ }
+ }
+
+ /**
+ * Authenticate the user
+ * <p>
+ * <p>
+ *
+ * @return true in all cases since this <code>LoginModule</code>
+ * should not be ignored.
+ * @throws LoginException if this <code>LoginModule</code>
+ * is unable to perform the authentication.
+ */
+ public boolean login() throws LoginException {
+
+ if (useTgtTicket) {
+ if (refreshKrb5Config) {
+ try {
+ if (debug) {
+ System.out.println("Refreshing Kerberos configuration");
+ }
+ sun.security.krb5.Config.refresh();
+ } catch (KrbException ke) {
+ LoginException le = new LoginException(ke.getMessage());
+ le.initCause(ke);
+ throw le;
+ }
+ }
+ String principalProperty = System.getProperty("sun.security.krb5.principal");
+ if (principalProperty != null) {
+ krb5PrincName = new StringBuffer(principalProperty);
+ } else {
+ if (princName != null) {
+ krb5PrincName = new StringBuffer(princName);
+ }
+ }
+
+ validateConfiguration();
+
+ if (krb5PrincName != null && krb5PrincName.toString().equals("*")) {
+ unboundServer = true;
+ }
+
+ // attempt the authentication by getting the username and pwd
+ // by prompting or configuration i.e. not from shared state
+
+ try {
+ attemptAuthentication(false);
+ succeeded = true;
+ cleanState();
+ return true;
+ } catch (LoginException e) {
+ // authentication failed -- clean out state
+ if (debug) {
+ System.out.println("\t\t[HasLoginModule] "
+ + "authentication failed \n"
+ + e.getMessage());
+ }
+ succeeded = false;
+ cleanState();
+ throw e;
+ }
+ } else {
+ succeeded = krb5LoginModule.login();
+ return succeeded;
+ }
+ }
+
+ /**
+ * Process the configuration options
+ * Get the TGT from Has Client
+ */
+
+ private void attemptAuthentication(boolean getPasswdFromSharedState)
+ throws LoginException {
+
+ /*
+ * Check the creds cache to see whether
+ * we have TGT for this client principal
+ */
+ if (krb5PrincName != null) {
+ try {
+ principal = new PrincipalName(krb5PrincName.toString(),
+ PrincipalName.KRB_NT_PRINCIPAL);
+ } catch (KrbException e) {
+ LoginException le = new LoginException(e.getMessage());
+ le.initCause(e);
+ throw le;
+ }
+ }
+
+ try {
+ if (useTgtTicket) {
+ if (debug) {
+ System.out.println("use tgt ticket to login, acquire TGT TICKET...");
+ }
+
+ HasClient hasClient = new HasClient(hadoopSecurityHas);
+ TgtTicket tgtTicket = null;
+ try {
+ tgtTicket = hasClient.requestTgt();
+ } catch (HasException e) {
+ LoginException le = new LoginException(e.getMessage());
+ le.initCause(e);
+ throw le;
+ }
+ Credential credential = new Credential(tgtTicket);
+ boolean[] flags = new boolean[7];
+ int flag = credential.getTicketFlags().getFlags();
+ for (int i = 6; i >= 0; i--) {
+ flags[i] = (flag & (1 << i)) != 0;
+ }
+ Date startTime = null;
+ if (credential.getStartTime() != null) {
+ startTime = credential.getStartTime().getValue();
+ }
+ cred = new Credentials(credential.getTicket().encode(),
+ credential.getClientName().getName(),
+ credential.getServerName().getName(),
+ credential.getKey().getKeyData(),
+ credential.getKey().getKeyType().getValue(),
+ flags,
+ credential.getAuthTime().getValue(),
+ startTime,
+ credential.getEndTime().getValue(),
+ credential.getRenewTill().getValue(),
+ null);
+
+ if (cred != null) {
+ // get the principal name from the ticket cache
+ if (principal == null) {
+ principal = cred.getClient();
+ }
+ }
+ if (debug) {
+ System.out.println("Principal is " + principal);
+ if (cred == null) {
+ System.out.println("null credentials from TGT Ticket");
+ }
+ }
+ }
+ } catch (KrbException e) {
+ LoginException le = new LoginException(e.getMessage());
+ le.initCause(e);
+ throw le;
+ } catch (IOException ioe) {
+ LoginException ie = new LoginException(ioe.getMessage());
+ ie.initCause(ioe);
+ throw ie;
+ }
+ }
+
+ private void validateConfiguration() throws LoginException {
+ if (doNotPrompt && !useTgtTicket) {
+ throw new LoginException("Configuration Error"
+ + " - either doNotPrompt should be "
+ + " false or"
+ + " useTgtTicket"
+ + " should be true");
+ }
+
+ if (krb5PrincName != null && krb5PrincName.toString().equals("*")) {
+ if (isInitiator) {
+ throw new LoginException("Configuration Error"
+ + " - principal cannot be * when isInitiator is true");
+ }
+ }
+ }
+
+ /**
+ * <p> This method is called if the LoginContext's
+ * overall authentication succeeded
+ *
+ * @return true if this LoginModule's own login and commit
+ * attempts succeeded, or false otherwise.
+ * @throws LoginException if the commit fails.
+ */
+
+ public boolean commit() throws LoginException {
+ if (debug) {
+ System.out.println("Login success? " + succeeded);
+ }
+
+ if (useTgtTicket) {
+ /*
+ * Let us add the Krb5 Creds to the Subject's
+ * private credentials. The credentials are of type
+ * KerberosKey or KerberosTicket
+ */
+ if (succeeded == false) {
+ return false;
+ } else {
+
+ if (isInitiator && (cred == null)) {
+ succeeded = false;
+ throw new LoginException("Null Client Credential");
+ }
+
+ if (subject.isReadOnly()) {
+ cleanKerberosCred();
+ throw new LoginException("Subject is Readonly");
+ }
+
+ /*
+ * Add the Principal (authenticated identity)
+ * to the Subject's principal set and
+ * add the credentials (TGT or Service key) to the
+ * Subject's private credentials
+ */
+
+ Set<Object> privCredSet = subject.getPrivateCredentials();
+ Set<java.security.Principal> princSet = subject.getPrincipals();
+ kerbClientPrinc = new KerberosPrincipal(principal.getName());
+
+ // create Kerberos Ticket
+ if (isInitiator) {
+ kerbTicket = Krb5Util.credsToTicket(cred);
+ }
+
+ // Let us add the kerbClientPrinc,kerbTicket
+
+ // We won't add "*" as a KerberosPrincipal
+ if (!unboundServer
+ && !princSet.contains(kerbClientPrinc)) {
+ princSet.add(kerbClientPrinc);
+ }
+
+ // add the TGT
+ if (kerbTicket != null) {
+ if (!privCredSet.contains(kerbTicket)) {
+ privCredSet.add(kerbTicket);
+ }
+ }
+ }
+ commitSucceeded = true;
+ if (debug) {
+ System.out.println("Commit Succeeded \n");
+ }
+ return true;
+ } else {
+ return krb5LoginModule.commit();
+ }
+ }
+
+ /**
+ * <p> This method is called if the LoginContext's
+ * overall authentication failed.
+ *
+ * @return false if this LoginModule's own login and/or commit attempts
+ * failed, and true otherwise.
+ * @throws LoginException if the abort fails.
+ */
+
+ public boolean abort() throws LoginException {
+ if (useTgtTicket) {
+ if (succeeded == false) {
+ return false;
+ } else if (succeeded == true && commitSucceeded == false) {
+ // login succeeded but overall authentication failed
+ succeeded = false;
+ cleanKerberosCred();
+ } else {
+ // overall authentication succeeded and commit succeeded,
+ // but someone else's commit failed
+ logout();
+ }
+ return true;
+ } else {
+ return krb5LoginModule.abort();
+ }
+ }
+
+ /**
+ * Logout the user.
+ * <p>
+ * <p> This method removes the <code>Krb5Principal</code>
+ * that was added by the <code>commit</code> method.
+ * <p>
+ * <p>
+ *
+ * @return true in all cases since this <code>LoginModule</code>
+ * should not be ignored.
+ * @throws LoginException if the logout fails.
+ */
+ public boolean logout() throws LoginException {
+
+ if (useTgtTicket) {
+ if (debug) {
+ System.out.println("\t\t[Krb5LoginModule]: "
+ + "Entering logout");
+ }
+
+ if (subject.isReadOnly()) {
+ cleanKerberosCred();
+ throw new LoginException("Subject is Readonly");
+ }
+
+ subject.getPrincipals().remove(kerbClientPrinc);
+ // Let us remove all Kerberos credentials stored in the Subject
+ Iterator<Object> it = subject.getPrivateCredentials().iterator();
+ while (it.hasNext()) {
+ Object o = it.next();
+ if (o instanceof KerberosTicket) {
+ it.remove();
+ }
+ }
+ // clean the kerberos ticket and keys
+ cleanKerberosCred();
+
+ succeeded = false;
+ commitSucceeded = false;
+ if (debug) {
+ System.out.println("\t\t[HasLoginModule]: "
+ + "logged out Subject");
+ }
+ return true;
+ } else {
+ return krb5LoginModule.logout();
+ }
+ }
+
+ /**
+ * Clean Kerberos credentials
+ */
+ private void cleanKerberosCred() throws LoginException {
+ // Clean the ticket and server key
+ try {
+ if (kerbTicket != null) {
+ kerbTicket.destroy();
+ }
+ } catch (DestroyFailedException e) {
+ throw new LoginException("Destroy Failed on Kerberos Private Credentials");
+ }
+ kerbTicket = null;
+ kerbClientPrinc = null;
+ }
+
+ /**
+ * Clean out the state
+ */
+ private void cleanState() {
+
+ if (!succeeded) {
+ // remove temp results for the next try
+ principal = null;
+ }
+ if (krb5PrincName != null && krb5PrincName.length() != 0) {
+ krb5PrincName.delete(0, krb5PrincName.length());
+ }
+ krb5PrincName = null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/be580566/has/has-client/src/main/resources/ssl-client.conf.template
----------------------------------------------------------------------
diff --git a/has/has-client/src/main/resources/ssl-client.conf.template b/has/has-client/src/main/resources/ssl-client.conf.template
new file mode 100644
index 0000000..c5ca70a
--- /dev/null
+++ b/has/has-client/src/main/resources/ssl-client.conf.template
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+ssl.client.truststore.location = _location_
+ssl.client.truststore.password = _password_
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/be580566/has/has-common/pom.xml
----------------------------------------------------------------------
diff --git a/has/has-common/pom.xml b/has/has-common/pom.xml
new file mode 100644
index 0000000..3046871
--- /dev/null
+++ b/has/has-common/pom.xml
@@ -0,0 +1,67 @@
+<?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">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>has-project</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>has-common</artifactId>
+ <description>HAS common</description>
+ <name>HAS common</name>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>token-provider</artifactId>
+ <version>${kerby.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerby-config</artifactId>
+ <version>${kerby.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-client-api-all</artifactId>
+ <version>${kerby.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-annotations</artifactId>
+ <version>3.0.0-alpha2</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>22.0-rc1-android</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>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/HasAdmin.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/HasAdmin.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/HasAdmin.java
new file mode 100644
index 0000000..94dc5df
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/has/common/HasAdmin.java
@@ -0,0 +1,140 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.hadoop.has.common;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Server side admin facilities from remote, similar to MIT kadmin remote mode.
+ */
+public interface HasAdmin {
+
+ /**
+ * Get the hadmin principal name.
+ *
+ * @return The hadmin principal name.
+ */
+ String getHadminPrincipal();
+
+ /**
+ * Add principal to backend.
+ *
+ * @param principal The principal to be added into backend
+ * @throws HasException e
+ */
+ void addPrincipal(String principal) throws HasException;
+
+ /**
+ * Add principal to backend.
+ *
+ * @param principal The principal to be added into backend
+ * @param password The password to create encryption key
+ * @throws HasException e
+ */
+ void addPrincipal(String principal, String password) throws HasException;
+
+ /**
+ * Export all the keys of the specified principal into the specified keytab
+ * file.
+ *
+ * @param keytabFile The keytab file
+ * @param principal The principal name
+ * @throws HasException e
+ */
+ void exportKeytab(File keytabFile, String principal) throws HasException;
+
+ /**
+ * Export all the keys of the specified principals into the specified keytab
+ * file.
+ *
+ * @param keytabFile The keytab file
+ * @param principals The principal names
+ * @throws HasException e
+ */
+ void exportKeytab(File keytabFile, List<String> principals) throws HasException;
+
+ /**
+ * Delete the principal in backend.
+ *
+ * @param principal The principal to be deleted from backend
+ * @throws HasException e
+ */
+ void deletePrincipal(String principal) throws HasException;
+
+ /**
+ * Rename the principal.
+ *
+ * @param oldPrincipalName The original principal name
+ * @param newPrincipalName The new principal name
+ * @throws HasException e
+ */
+ void renamePrincipal(String oldPrincipalName,
+ String newPrincipalName) throws HasException;
+
+ /**
+ * Get all the principal names from backend.
+ *
+ * @return principal list
+ * @throws HasException e
+ */
+ List<String> getPrincipals() throws HasException;
+
+ /**
+ * Get all the principal names that meets the pattern
+ *
+ * @param globString The glob string for matching
+ * @return Principal names
+ * @throws HasException e
+ */
+ List<String> getPrincipals(String globString) throws HasException;
+
+ /**
+ * Change the password of specified principal.
+ *
+ * @param principal The principal to be updated password
+ * @param newPassword The new password
+ * @throws HasException e
+ */
+// void changePassword(String principal, String newPassword) throws HasException;
+
+ /**
+ * Update the random keys of specified principal.
+ *
+ * @param principal The principal to be updated keys
+ * @throws HasException e
+ */
+// void updateKeys(String principal) throws HasException;
+
+ /**
+ * Release any resources associated.
+ *
+ * @throws HasException e
+ */
+// void release() throws HasException;
+
+ String addPrincByRole(String host, String role) throws HasException;
+
+ File getKeytabByHostAndRole(String host, String role) throws HasException;
+
+ int size() throws HasException;
+
+ void setEnableOfConf(String isEnable) throws HasException;
+}
http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/HasConfig.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/HasConfig.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/HasConfig.java
new file mode 100644
index 0000000..3fc0998
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/HasConfigKey.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/HasConfigKey.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/HasConfigKey.java
new file mode 100644
index 0000000..07db8d4
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/HasException.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/HasException.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/HasException.java
new file mode 100644
index 0000000..f8fc3b3
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthToken.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthToken.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthToken.java
new file mode 100644
index 0000000..c7a18da
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.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
+ */
+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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthenticatedURL.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthenticatedURL.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthenticatedURL.java
new file mode 100644
index 0000000..ccd7ea4
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.has.common.spnego;
+
+import org.apache.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthenticationException.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthenticationException.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/AuthenticationException.java
new file mode 100644
index 0000000..62a5d38
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/Authenticator.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/Authenticator.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/Authenticator.java
new file mode 100644
index 0000000..91eb1a0
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.has.common.spnego;
+
+import org.apache.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosAuthenticator.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosAuthenticator.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosAuthenticator.java
new file mode 100644
index 0000000..c785430
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosAuthenticator.java
@@ -0,0 +1,359 @@
+/**
+ * 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.hadoop.has.common.spnego;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.hadoop.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.hadoop.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>
+ */
+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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosHasAuthenticator.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosHasAuthenticator.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosHasAuthenticator.java
new file mode 100644
index 0000000..7db0551
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.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/be580566/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosUtil.java
----------------------------------------------------------------------
diff --git a/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosUtil.java b/has/has-common/src/main/java/org/apache/hadoop/has/common/spnego/KerberosUtil.java
new file mode 100644
index 0000000..b6e330d
--- /dev/null
+++ b/has/has-common/src/main/java/org/apache/hadoop/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.hadoop.has.common.spnego;
+
+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 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 static org.apache.hadoop.has.common.util.PlatformName.IBM_JAVA;
+
+/**
+ * Borrow the class from Apache Hadoop
+ */
+
+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();
+ }
+}