You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sw...@apache.org on 2013/08/22 20:06:05 UTC

git commit: AMBARI-2941. Ambari server gets auth exception while getting JMX metrics from namenode in secure cluster. (Dilli Arumugam via swagle)

Updated Branches:
  refs/heads/trunk 60e1efd0d -> a194cb0c2


AMBARI-2941. Ambari server gets auth exception while getting JMX metrics from namenode in secure cluster. (Dilli Arumugam via swagle)


Project: http://git-wip-us.apache.org/repos/asf/incubator-ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ambari/commit/a194cb0c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ambari/tree/a194cb0c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ambari/diff/a194cb0c

Branch: refs/heads/trunk
Commit: a194cb0c22eee02a37233eae0ac736b71144753e
Parents: 60e1efd
Author: Siddharth Wagle <sw...@hortonworks.com>
Authored: Thu Aug 22 11:05:34 2013 -0700
Committer: Siddharth Wagle <sw...@hortonworks.com>
Committed: Thu Aug 22 11:05:34 2013 -0700

----------------------------------------------------------------------
 ambari-server/conf/unix/ambari-env.sh           |   2 +
 ambari-server/conf/unix/krb5JAASLogin.conf      |  13 ++
 .../controller/internal/AppCookieManager.java   | 209 +++++++++++++++++++
 .../controller/internal/URLStreamProvider.java  |  97 +++++++--
 .../internal/AppCookieManagerTest.java          |  52 +++++
 5 files changed, 353 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/a194cb0c/ambari-server/conf/unix/ambari-env.sh
----------------------------------------------------------------------
diff --git a/ambari-server/conf/unix/ambari-env.sh b/ambari-server/conf/unix/ambari-env.sh
index 42361cb..6d1a595 100644
--- a/ambari-server/conf/unix/ambari-env.sh
+++ b/ambari-server/conf/unix/ambari-env.sh
@@ -13,4 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+
 AMBARI_PASSHPHRASE="DEV"
+export AMBARI_JVM_ARGS='-Xms512m -Xmx2048m -Djava.security.auth.login.config=/etc/ambari-server/conf/krb5JAASLogin.conf -Djava.security.krb5.conf=/etc/krb5.conf -Djavax.security.auth.useSubjectCredsOnly=false'

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/a194cb0c/ambari-server/conf/unix/krb5JAASLogin.conf
----------------------------------------------------------------------
diff --git a/ambari-server/conf/unix/krb5JAASLogin.conf b/ambari-server/conf/unix/krb5JAASLogin.conf
new file mode 100644
index 0000000..b667081
--- /dev/null
+++ b/ambari-server/conf/unix/krb5JAASLogin.conf
@@ -0,0 +1,13 @@
+com.sun.security.jgss.initiate {
+    com.sun.security.auth.module.Krb5LoginModule required 
+    renewTGT=true
+    doNotPrompt=true
+    useKeyTab=true
+    keyTab="/etc/security/keytabs/ambari.keytab"
+    principal="ambari@EXAMPLE.COM"
+    isInitiator=true
+    storeKey=true
+    useTicketCache=true
+    client=true; 
+};
+

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/a194cb0c/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AppCookieManager.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AppCookieManager.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AppCookieManager.java
new file mode 100644
index 0000000..2c21086
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AppCookieManager.java
@@ -0,0 +1,209 @@
+/**
+ * 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.ambari.server.controller.internal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+/**
+ * Handles SPNego authentication as a client of hadoop service, caches
+ * hadoop.auth cookie returned by hadoop service on successful SPNego
+ * authentication. Refreshes hadoop.auth cookie on demand if the cookie has
+ * expired.
+ * 
+ */
+public class AppCookieManager {
+
+  static final String HADOOP_AUTH = "hadoop.auth";
+  private static final String HADOOP_AUTH_EQ = "hadoop.auth=";
+  private static final String SET_COOKIE = "Set-Cookie";
+
+  private static final EmptyJaasCredentials EMPTY_JAAS_CREDENTIALS = new EmptyJaasCredentials();
+
+  private Map<String, String> endpointCookieMap = new ConcurrentHashMap<String, String>();
+  private static Log LOG = LogFactory.getLog(AppCookieManager.class);
+
+  /**
+   * Utility method to exercise AppCookieManager directly
+   * @param args element 0 of args should be a URL to hadoop service protected by SPengo
+   * @throws IOException in case of errors
+   */
+  public static void main(String[] args) throws IOException {
+    new AppCookieManager().getAppCookie(args[0], false);
+  }
+
+  public AppCookieManager() {
+  }
+
+  /**
+   * Returns hadoop.auth cookie, doing needed SPNego authentication
+   * 
+   * @param endpoint
+   *          the URL of the Hadoop service
+   * @param refresh
+   *          flag indicating wehther to refresh the cookie, if
+   *          <code>true</code>, we do a new SPNego authentication and refresh
+   *          the cookie even if the cookie already exists in local cache
+   * @return hadoop.auth cookie value
+   * @throws IOException
+   *           in case of problem getting hadoop.auth cookie
+   */
+  public String getAppCookie(String endpoint, boolean refresh)
+      throws IOException {
+
+    HttpUriRequest outboundRequest = new HttpGet(endpoint);
+    URI uri = outboundRequest.getURI();
+    String scheme = uri.getScheme();
+    String host = uri.getHost();
+    int port = uri.getPort();
+    String path = uri.getPath();
+    if (!refresh) {
+      String appCookie = endpointCookieMap.get(endpoint);
+      if (appCookie != null) {
+        return appCookie;
+      }
+    }
+
+    clearAppCookie(endpoint);
+    
+    DefaultHttpClient client = new DefaultHttpClient();
+    SPNegoSchemeFactory spNegoSF = new SPNegoSchemeFactory(/* stripPort */true);
+    // spNegoSF.setSpengoGenerator(new BouncySpnegoTokenGenerator());
+    client.getAuthSchemes().register(AuthPolicy.SPNEGO, spNegoSF);
+    client.getCredentialsProvider().setCredentials(
+        new AuthScope(/* host */null, /* port */-1, /* realm */null),
+        EMPTY_JAAS_CREDENTIALS);
+
+    String hadoopAuthCookie = null;
+    HttpResponse httpResponse = null;
+    try {
+      HttpHost httpHost = new HttpHost(host, port, scheme);
+      HttpRequest httpRequest = new HttpOptions(path);
+      httpResponse = client.execute(httpHost, httpRequest);
+      Header[] headers = httpResponse.getHeaders(SET_COOKIE);
+      hadoopAuthCookie = getHadoopAuthCookieValue(headers);
+      if (hadoopAuthCookie == null) {
+        LOG.error("SPNego authentication failed, can not get hadoop.auth cookie for URL: " + endpoint);
+        throw new IOException(
+            "SPNego authentication failed, can not get hadoop.auth cookie");
+      }
+    } finally {
+      if (httpResponse != null) {
+        HttpEntity entity = httpResponse.getEntity();
+        if (entity != null) {
+          entity.getContent().close();
+        }
+      }
+
+    }
+ 
+    hadoopAuthCookie = HADOOP_AUTH_EQ + quote(hadoopAuthCookie);
+    setAppCookie(endpoint, hadoopAuthCookie);
+    if (LOG.isInfoEnabled()) {
+      LOG.info("Successful SPNego authentication to URL:" + uri.toString());
+    }
+    return hadoopAuthCookie;
+  }
+
+  
+  /**
+   * Returns the cached app cookie
+   *  @param endpoint the hadoop end point we authenticate to
+   * @return the cached app cookie, can be null
+   */
+  public String getCachedAppCookie(String endpoint) {
+    return endpointCookieMap.get(endpoint);
+  }
+  
+  /**
+   *  Sets the cached app cookie i cache
+   *  @param endpoint the hadoop end point we authenticate to
+   *  @param appCookie the app cookie
+   */
+  private void setAppCookie(String endpoint, String appCookie) {
+    endpointCookieMap.put(endpoint, appCookie);
+  }
+
+  /**
+   *  Clears the cached app cookie
+   *  @param endpoint the hadoop end point we authenticate to
+   */
+  private void clearAppCookie(String endpoint) {
+    endpointCookieMap.remove(endpoint);
+  }
+  
+  static String quote(String s) {
+    return s == null ? s : "\"" + s + "\"";
+  }
+
+  static String getHadoopAuthCookieValue(Header[] headers) {
+    if (headers == null) {
+      return null;
+    }
+    for (Header header : headers) {
+      HeaderElement[] elements = header.getElements();
+      for (HeaderElement element : elements) {
+        String cookieName = element.getName();
+        if (cookieName.equals(HADOOP_AUTH)) {
+          if (element.getValue() != null) {
+            String trimmedVal = element.getValue().trim();
+            if (!trimmedVal.isEmpty()) {
+              return trimmedVal;
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+
+  private static class EmptyJaasCredentials implements Credentials {
+
+    public String getPassword() {
+      return null;
+    }
+
+    public Principal getUserPrincipal() {
+      return null;
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/a194cb0c/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
index b7d972e..12a0ac6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
@@ -22,57 +22,112 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
 import java.security.KeyStore;
 
-import org.apache.ambari.server.controller.utilities.StreamProvider;
-
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManagerFactory;
 
+import org.apache.ambari.server.controller.utilities.StreamProvider;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicHeader;
+
 /**
  * URL based implementation of a stream provider.
  */
 public class URLStreamProvider implements StreamProvider {
 
+  private static final String COOKIE = "Cookie";
+  private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
+  private static final String NEGOTIATE = "Negotiate";
+  private static Log LOG = LogFactory.getLog(URLStreamProvider.class);
+
   private final int connTimeout;
   private final int readTimeout;
   private final String path;
   private final String password;
   private final String type;
   private volatile SSLSocketFactory sslSocketFactory = null;
+  private AppCookieManager appCookieManager;
 
   /**
    * Provide the connection timeout for the underlying connection.
-   *
-   * @param connectionTimeout  time, in milliseconds, to attempt a connection
-   * @param readTimeout        the read timeout in milliseconds
+   * 
+   * @param connectionTimeout
+   *          time, in milliseconds, to attempt a connection
+   * @param readTimeout
+   *          the read timeout in milliseconds
    */
-  public URLStreamProvider(int connectionTimeout, int readTimeout,
-                           String path, String password, String type) {
+  public URLStreamProvider(int connectionTimeout, int readTimeout, String path,
+      String password, String type) {
+
     this.connTimeout = connectionTimeout;
     this.readTimeout = readTimeout;
-    this.path        = path;
-    this.password    = password;
-    this.type        = type;
+    this.path = path;
+    this.password = password;
+    this.type = type;
+    appCookieManager = new AppCookieManager();
   }
-  
+
   @Override
   public InputStream readFrom(String spec) throws IOException {
 
-    URLConnection connection = spec.startsWith("https") ?
-        getSSLConnection(spec) : getConnection(spec);
+    HttpURLConnection connection = spec.startsWith("https") ? 
+        (HttpURLConnection)getSSLConnection(spec)
+        : (HttpURLConnection)getConnection(spec);
 
+    String appCookie = appCookieManager.getCachedAppCookie(spec);
+    if (appCookie != null) {
+      if (LOG.isInfoEnabled()) {
+        LOG.info("Using cached app cookie for URL:" + spec);
+      }
+      connection.setRequestProperty(COOKIE, appCookie);
+    }
     connection.setConnectTimeout(connTimeout);
     connection.setReadTimeout(readTimeout);
     connection.setDoOutput(true);
 
-    return connection.getInputStream();
-  }
+    int statusCode = connection.getResponseCode();
+    if (statusCode == HttpStatus.SC_UNAUTHORIZED ) {
+      String wwwAuthHeader = connection.getHeaderField(WWW_AUTHENTICATE);
+      if (LOG.isInfoEnabled()) {
+        LOG.info("Received WWW-Authentication header:" + wwwAuthHeader + ", for URL:" + spec);
+      }
+      if (wwwAuthHeader != null && 
+          wwwAuthHeader.trim().startsWith(NEGOTIATE)) {
+        //connection.getInputStream().close();
+        connection = spec.startsWith("https") ? 
+            (HttpURLConnection)getSSLConnection(spec)
+            : (HttpURLConnection)getConnection(spec);
+        appCookie = appCookieManager.getAppCookie(spec, true);
+        connection.setRequestProperty(COOKIE, appCookie);
+        connection.setConnectTimeout(connTimeout);
+        connection.setReadTimeout(readTimeout);
+        connection.setDoOutput(true);
+        return connection.getInputStream();
+      } else {
+        // no supported authentication type found
+        // we would let the original response propogate
+        LOG.error("Unsupported WWW-Authentication header:" + wwwAuthHeader+ ", for URL:" + spec);
+        return connection.getInputStream();
+      }
+    } else {
+      // not a 401 Unauthorized status code
+      // we would let the original response propogate
+      return connection.getInputStream();
+    }
 
+  }
 
   // ----- helper methods ----------------------------------------------------
 
@@ -88,14 +143,15 @@ public class URLStreamProvider implements StreamProvider {
       synchronized (this) {
         if (sslSocketFactory == null) {
           try {
-            FileInputStream in    = new FileInputStream(new File(path));
-            KeyStore        store = KeyStore.getInstance(type == null ? KeyStore.getDefaultType() : type);
+            FileInputStream in = new FileInputStream(new File(path));
+            KeyStore store = KeyStore.getInstance(type == null ? KeyStore
+                .getDefaultType() : type);
 
             store.load(in, password.toCharArray());
             in.close();
 
-            TrustManagerFactory tmf =
-                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            TrustManagerFactory tmf = TrustManagerFactory
+                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
 
             tmf.init(store);
             SSLContext context = SSLContext.getInstance("TLS");
@@ -108,7 +164,8 @@ public class URLStreamProvider implements StreamProvider {
         }
       }
     }
-    HttpsURLConnection connection = (HttpsURLConnection)(new URL(spec).openConnection());
+    HttpsURLConnection connection = (HttpsURLConnection) (new URL(spec)
+        .openConnection());
 
     connection.setSSLSocketFactory(sslSocketFactory);
 

http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/a194cb0c/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AppCookieManagerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AppCookieManagerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AppCookieManagerTest.java
new file mode 100644
index 0000000..82a876e
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AppCookieManagerTest.java
@@ -0,0 +1,52 @@
+/**
+ * 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.ambari.server.controller.internal;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.apache.http.Header;
+import org.apache.http.message.BasicHeader;
+import org.junit.Test;
+
+public class AppCookieManagerTest {
+
+  @Test
+  public void getCachedKnoxAppCookie() {
+    assertNull(new AppCookieManager().getCachedAppCookie("http://dummy"));
+  }
+
+  @Test
+  public void getHadoopAuthCookieValueWithNullHeaders() {
+    assertNull(AppCookieManager.getHadoopAuthCookieValue(null));
+  }
+  
+  @Test
+  public void getHadoopAuthCookieValueWitEmptylHeaders() {
+    assertNull(AppCookieManager.getHadoopAuthCookieValue(new Header[0]));
+  }
+  
+  @Test
+  public void getHadoopAuthCookieValueWithValidlHeaders() {
+    Header[] headers = new Header[1];
+    headers[0] = new BasicHeader("Set-Cookie", AppCookieManager.HADOOP_AUTH + "=dummyvalue");
+    assertNotNull(AppCookieManager.getHadoopAuthCookieValue(headers));
+  }
+  
+}