You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by pr...@apache.org on 2018/05/04 18:12:38 UTC

hive git commit: HIVE-19415: Support CORS for all HS2 web endpoints (Prasanth Jayachandran reviewed by Sergey Shelukhin)

Repository: hive
Updated Branches:
  refs/heads/branch-3 5e5cd02a3 -> d1950a06a


HIVE-19415: Support CORS for all HS2 web endpoints (Prasanth Jayachandran reviewed by Sergey Shelukhin)


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

Branch: refs/heads/branch-3
Commit: d1950a06a4fd488bed56cf168f46295156c11c5b
Parents: 5e5cd02
Author: Prasanth Jayachandran <pr...@apache.org>
Authored: Fri May 4 11:10:19 2018 -0700
Committer: Prasanth Jayachandran <pr...@apache.org>
Committed: Fri May 4 11:11:58 2018 -0700

----------------------------------------------------------------------
 .../org/apache/hadoop/hive/conf/HiveConf.java   | 10 ++++
 .../java/org/apache/hive/http/HttpServer.java   | 46 +++++++++++++++
 .../apache/hive/jdbc/TestActivePassiveHA.java   | 62 +++++++++++++++-----
 .../apache/hive/service/server/HiveServer2.java | 18 ++++++
 4 files changed, 122 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hive/blob/d1950a06/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
----------------------------------------------------------------------
diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
index 572e993..9a0d8a9 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -3022,6 +3022,16 @@ public class HiveConf extends Configuration {
         "The maximum number of past queries to show in HiverSever2 WebUI."),
     HIVE_SERVER2_WEBUI_USE_PAM("hive.server2.webui.use.pam", false,
         "If true, the HiveServer2 WebUI will be secured with PAM."),
+    HIVE_SERVER2_WEBUI_ENABLE_CORS("hive.server2.webui.enable.cors", false,
+      "Whether to enable cross origin requests (CORS)\n"),
+    HIVE_SERVER2_WEBUI_CORS_ALLOWED_ORIGINS("hive.server2.webui.cors.allowed.origins", "*",
+      "Comma separated list of origins that are allowed when CORS is enabled.\n"),
+    HIVE_SERVER2_WEBUI_CORS_ALLOWED_METHODS("hive.server2.webui.cors.allowed.methods", "GET,POST,DELETE,HEAD",
+      "Comma separated list of http methods that are allowed when CORS is enabled.\n"),
+    HIVE_SERVER2_WEBUI_CORS_ALLOWED_HEADERS("hive.server2.webui.cors.allowed.headers",
+      "X-Requested-With,Content-Type,Accept,Origin",
+      "Comma separated list of http headers that are allowed when CORS is enabled.\n"),
+
 
     // Tez session settings
     HIVE_SERVER2_ACTIVE_PASSIVE_HA_ENABLE("hive.server2.active.passive.ha.enable", false,

http://git-wip-us.apache.org/repos/asf/hive/blob/d1950a06/common/src/java/org/apache/hive/http/HttpServer.java
----------------------------------------------------------------------
diff --git a/common/src/java/org/apache/hive/http/HttpServer.java b/common/src/java/org/apache/hive/http/HttpServer.java
index 93b11e3..3cb7a33 100644
--- a/common/src/java/org/apache/hive/http/HttpServer.java
+++ b/common/src/java/org/apache/hive/http/HttpServer.java
@@ -46,6 +46,7 @@ import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
 import org.apache.hadoop.security.authorize.AccessControlList;
 import org.apache.hadoop.hive.common.classification.InterfaceAudience;
+import org.apache.hadoop.security.http.CrossOriginFilter;
 import org.apache.hive.http.security.PamAuthenticator;
 import org.apache.hive.http.security.PamConstraint;
 import org.apache.hive.http.security.PamConstraintMapping;
@@ -125,6 +126,10 @@ public class HttpServer {
     private boolean useSPNEGO;
     private boolean useSSL;
     private boolean usePAM;
+    private boolean enableCORS;
+    private String allowedOrigins;
+    private String allowedMethods;
+    private String allowedHeaders;
     private PamAuthenticator pamAuthenticator;
     private String contextRootRewriteTarget = "/index.html";
     private final List<Pair<String, Class<? extends HttpServlet>>> servlets =
@@ -199,6 +204,26 @@ public class HttpServer {
       return this;
     }
 
+    public Builder setEnableCORS(boolean enableCORS) {
+      this.enableCORS = enableCORS;
+      return this;
+    }
+
+    public Builder setAllowedOrigins(String allowedOrigins) {
+      this.allowedOrigins = allowedOrigins;
+      return this;
+    }
+
+    public Builder setAllowedMethods(String allowedMethods) {
+      this.allowedMethods = allowedMethods;
+      return this;
+    }
+
+    public Builder setAllowedHeaders(String allowedHeaders) {
+      this.allowedHeaders = allowedHeaders;
+      return this;
+    }
+
     public Builder setSPNEGOPrincipal(String principal) {
       this.spnegoPrincipal = principal;
       return this;
@@ -401,6 +426,23 @@ public class HttpServer {
   }
 
   /**
+   * Setup cross-origin requests (CORS) filter.
+   * @param b - builder
+   */
+  private void setupCORSFilter(Builder b) {
+    FilterHolder holder = new FilterHolder();
+    holder.setClassName(CrossOriginFilter.class.getName());
+    Map<String, String> params = new HashMap<>();
+    params.put(CrossOriginFilter.ALLOWED_ORIGINS, b.allowedOrigins);
+    params.put(CrossOriginFilter.ALLOWED_METHODS, b.allowedMethods);
+    params.put(CrossOriginFilter.ALLOWED_HEADERS, b.allowedHeaders);
+    holder.setInitParameters(params);
+
+    ServletHandler handler = webAppContext.getServletHandler();
+    handler.addFilterWithMapping(holder, "/*", FilterMapping.ALL);
+  }
+
+  /**
    * Create a channel connector for "http/https" requests
    */
   Connector createChannelConnector(int queueSize, Builder b) {
@@ -474,6 +516,10 @@ public class HttpServer {
       setupSpnegoFilter(b);
     }
 
+    if (b.enableCORS) {
+      setupCORSFilter(b);
+    }
+
     initializeWebServer(b, threadPool.getMaxThreads());
   }
 

http://git-wip-us.apache.org/repos/asf/hive/blob/d1950a06/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestActivePassiveHA.java
----------------------------------------------------------------------
diff --git a/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestActivePassiveHA.java b/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestActivePassiveHA.java
index fb846b4..c55271f 100644
--- a/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestActivePassiveHA.java
+++ b/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestActivePassiveHA.java
@@ -19,7 +19,9 @@
 package org.apache.hive.jdbc;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -38,6 +40,7 @@ import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.HttpMethodBase;
 import org.apache.commons.httpclient.methods.DeleteMethod;
 import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.OptionsMethod;
 import org.apache.curator.test.TestingServer;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
@@ -51,6 +54,7 @@ import org.apache.hive.service.server.HS2ActivePassiveHARegistryClient;
 import org.apache.hive.service.server.HiveServer2Instance;
 import org.apache.hive.service.server.TestHS2HttpServerPam;
 import org.apache.hive.service.servlet.HS2Peers;
+import org.apache.http.HttpException;
 import org.apache.http.HttpHeaders;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.junit.After;
@@ -317,6 +321,8 @@ public class TestActivePassiveHA {
 
   @Test(timeout = 60000)
   public void testManualFailover() throws Exception {
+    hiveConf1.setBoolVar(ConfVars.HIVE_SERVER2_WEBUI_ENABLE_CORS, true);
+    hiveConf2.setBoolVar(ConfVars.HIVE_SERVER2_WEBUI_ENABLE_CORS, true);
     setPamConfs(hiveConf1);
     setPamConfs(hiveConf2);
     PamAuthenticator pamAuthenticator1 = new TestHS2HttpServerPam.TestPamAuthenticator(hiveConf1);
@@ -337,41 +343,43 @@ public class TestActivePassiveHA {
       // when we start miniHS2_1 will be leader (sequential start)
       assertEquals(true, miniHS2_1.getIsLeaderTestFuture().get());
       assertEquals(true, miniHS2_1.isLeader());
-      assertEquals("true", sendGet(url1, true));
+      assertEquals("true", sendGet(url1, true, true));
 
       // trigger failover on miniHS2_1
-      String resp = sendDelete(url1, true);
+      String resp = sendDelete(url1, true, true);
       assertTrue(resp.contains("Failover successful!"));
 
       // make sure miniHS2_1 is not leader
       assertEquals(true, miniHS2_1.getNotLeaderTestFuture().get());
       assertEquals(false, miniHS2_1.isLeader());
-      assertEquals("false", sendGet(url1, true));
+      assertEquals("false", sendGet(url1, true, true));
 
       // make sure miniHS2_2 is the new leader
       assertEquals(true, miniHS2_2.getIsLeaderTestFuture().get());
       assertEquals(true, miniHS2_2.isLeader());
-      assertEquals("true", sendGet(url2, true));
+      assertEquals("true", sendGet(url2, true, true));
 
       // send failover request again to miniHS2_1 and get a failure
-      resp = sendDelete(url1, true);
+      resp = sendDelete(url1, true, true);
       assertTrue(resp.contains("Cannot failover an instance that is not a leader"));
       assertEquals(true, miniHS2_1.getNotLeaderTestFuture().get());
       assertEquals(false, miniHS2_1.isLeader());
 
       // send failover request to miniHS2_2 and make sure miniHS2_1 takes over (returning back to leader, test listeners)
-      resp = sendDelete(url2, true);
+      resp = sendDelete(url2, true, true);
       assertTrue(resp.contains("Failover successful!"));
       assertEquals(true, miniHS2_1.getIsLeaderTestFuture().get());
       assertEquals(true, miniHS2_1.isLeader());
-      assertEquals("true", sendGet(url1, true));
+      assertEquals("true", sendGet(url1, true, true));
       assertEquals(true, miniHS2_2.getNotLeaderTestFuture().get());
-      assertEquals("false", sendGet(url2, true));
+      assertEquals("false", sendGet(url2, true, true));
       assertEquals(false, miniHS2_2.isLeader());
     } finally {
       // revert configs to not affect other tests
       unsetPamConfs(hiveConf1);
       unsetPamConfs(hiveConf2);
+      hiveConf1.unset(ConfVars.HIVE_SERVER2_WEBUI_ENABLE_CORS.varname);
+      hiveConf2.unset(ConfVars.HIVE_SERVER2_WEBUI_ENABLE_CORS.varname);
     }
   }
 
@@ -533,20 +541,40 @@ public class TestActivePassiveHA {
   }
 
   private String sendGet(String url, boolean enableAuth) throws Exception {
-    return sendAuthMethod(new GetMethod(url), enableAuth);
+    return sendAuthMethod(new GetMethod(url), enableAuth, false);
+  }
+
+  private String sendGet(String url, boolean enableAuth, boolean enableCORS) throws Exception {
+    return sendAuthMethod(new GetMethod(url), enableAuth, enableCORS);
   }
 
   private String sendDelete(String url, boolean enableAuth) throws Exception {
-    return sendAuthMethod(new DeleteMethod(url), enableAuth);
+    return sendAuthMethod(new DeleteMethod(url), enableAuth, false);
+  }
+
+  private String sendDelete(String url, boolean enableAuth, boolean enableCORS) throws Exception {
+    return sendAuthMethod(new DeleteMethod(url), enableAuth, enableCORS);
   }
 
-  private String sendAuthMethod(HttpMethodBase method, boolean enableAuth) throws Exception {
+  private String sendAuthMethod(HttpMethodBase method, boolean enableAuth, boolean enableCORS) throws Exception {
     HttpClient client = new HttpClient();
     try {
       if (enableAuth) {
-        String userPass = ADMIN_USER + ":" + ADMIN_PASSWORD;
-        method.addRequestHeader(HttpHeaders.AUTHORIZATION,
-          "Basic " + new String(Base64.getEncoder().encode(userPass.getBytes())));
+        setupAuthHeaders(method);
+      }
+      // CORS check
+      if (enableCORS) {
+        String origin = "http://example.com";
+        OptionsMethod optionsMethod = new OptionsMethod(method.getURI().toString());
+        optionsMethod.addRequestHeader("Origin", origin);
+        setupAuthHeaders(optionsMethod);
+        int statusCode = client.executeMethod(optionsMethod);
+        if (statusCode == 200) {
+          assertNotNull(optionsMethod.getResponseHeader("Access-Control-Allow-Origin"));
+          assertEquals(origin, optionsMethod.getResponseHeader("Access-Control-Allow-Origin").getValue());
+        } else {
+          fail("CORS returned: " + statusCode + " Error: " + optionsMethod.getStatusLine().getReasonPhrase());
+        }
       }
       int statusCode = client.executeMethod(method);
       if (statusCode == 200) {
@@ -559,6 +587,12 @@ public class TestActivePassiveHA {
     }
   }
 
+  private void setupAuthHeaders(final HttpMethodBase method) {
+    String userPass = ADMIN_USER + ":" + ADMIN_PASSWORD;
+    method.addRequestHeader(HttpHeaders.AUTHORIZATION,
+      "Basic " + new String(Base64.getEncoder().encode(userPass.getBytes())));
+  }
+
   private Map<String, String> getConfOverlay(final String instanceId) {
     Map<String, String> confOverlay = new HashMap<>();
     confOverlay.put("hive.server2.zookeeper.publish.configs", "true");

http://git-wip-us.apache.org/repos/asf/hive/blob/d1950a06/service/src/java/org/apache/hive/service/server/HiveServer2.java
----------------------------------------------------------------------
diff --git a/service/src/java/org/apache/hive/service/server/HiveServer2.java b/service/src/java/org/apache/hive/service/server/HiveServer2.java
index e373628..8584250 100644
--- a/service/src/java/org/apache/hive/service/server/HiveServer2.java
+++ b/service/src/java/org/apache/hive/service/server/HiveServer2.java
@@ -342,6 +342,24 @@ public class HiveServer2 extends CompositeService {
             builder.setSPNEGOKeytab(spnegoKeytab);
             builder.setUseSPNEGO(true);
           }
+          if (hiveConf.getBoolVar(ConfVars.HIVE_SERVER2_WEBUI_ENABLE_CORS)) {
+            builder.setEnableCORS(true);
+            String allowedOrigins = hiveConf.getVar(ConfVars.HIVE_SERVER2_WEBUI_CORS_ALLOWED_ORIGINS);
+            String allowedMethods = hiveConf.getVar(ConfVars.HIVE_SERVER2_WEBUI_CORS_ALLOWED_METHODS);
+            String allowedHeaders = hiveConf.getVar(ConfVars.HIVE_SERVER2_WEBUI_CORS_ALLOWED_HEADERS);
+            if (Strings.isBlank(allowedOrigins) || Strings.isBlank(allowedMethods) || Strings.isBlank(allowedHeaders)) {
+              throw new IllegalArgumentException("CORS enabled. But " +
+                ConfVars.HIVE_SERVER2_WEBUI_CORS_ALLOWED_ORIGINS.varname + "/" +
+                ConfVars.HIVE_SERVER2_WEBUI_CORS_ALLOWED_METHODS.varname + "/" +
+                ConfVars.HIVE_SERVER2_WEBUI_CORS_ALLOWED_HEADERS.varname + "/" +
+                " is not configured");
+            }
+            builder.setAllowedOrigins(allowedOrigins);
+            builder.setAllowedMethods(allowedMethods);
+            builder.setAllowedHeaders(allowedHeaders);
+            LOG.info("CORS enabled - allowed-origins: {} allowed-methods: {} allowed-headers: {}", allowedOrigins,
+              allowedMethods, allowedHeaders);
+          }
           if (hiveConf.getBoolVar(ConfVars.HIVE_SERVER2_WEBUI_USE_PAM)) {
             if (hiveConf.getBoolVar(ConfVars.HIVE_SERVER2_WEBUI_USE_SSL)) {
               String hiveServer2PamServices = hiveConf.getVar(ConfVars.HIVE_SERVER2_PAM_SERVICES);