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 <prasan...@apache.org> Authored: Fri May 4 11:10:19 2018 -0700 Committer: Prasanth Jayachandran <prasan...@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);