You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by th...@apache.org on 2013/08/30 10:23:27 UTC
svn commit: r1518912 - in /hive/trunk/hcatalog:
src/test/e2e/templeton/tests/doas.conf
webhcat/svr/src/main/java/org/apache/hcatalog/templeton/ProxyUserSupport.java
Author: thejas
Date: Fri Aug 30 08:23:27 2013
New Revision: 1518912
URL: http://svn.apache.org/r1518912
Log:
Add missing files from - HIVE-4601 : WebHCat needs to support proxy users
Added:
hive/trunk/hcatalog/src/test/e2e/templeton/tests/doas.conf
hive/trunk/hcatalog/webhcat/svr/src/main/java/org/apache/hcatalog/templeton/ProxyUserSupport.java
Added: hive/trunk/hcatalog/src/test/e2e/templeton/tests/doas.conf
URL: http://svn.apache.org/viewvc/hive/trunk/hcatalog/src/test/e2e/templeton/tests/doas.conf?rev=1518912&view=auto
==============================================================================
--- hive/trunk/hcatalog/src/test/e2e/templeton/tests/doas.conf (added)
+++ hive/trunk/hcatalog/src/test/e2e/templeton/tests/doas.conf Fri Aug 30 08:23:27 2013
@@ -0,0 +1,155 @@
+# 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.
+
+###############################################################################
+# curl command tests for templeton
+#
+#
+
+#use Yahoo::Miners::Test::PigSetup;
+
+#PigSetup::setup();
+
+#my $me = `whoami`;
+#chomp $me;
+
+$cfg =
+{
+ 'driver' => 'Curl',
+
+ 'groups' =>
+ [
+##=============================================================================================================
+#This suite tests support for doAs user in WebHCat.
+#This suite of tests requires some set up. They test that security context is properly propagated.
+#These tests are meant to run in File based security mode. Also, 2 users need to be created.
+#See README.txt for details on set up.
+#
+#
+
+ {
+ 'name' => 'doAsTests',
+ 'tests' =>
+ [
+
+ {
+ #drop table if exists to clean up from previous run
+ 'num' => 1,
+ 'method' => 'DELETE',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab2?user.name=:UNAME:&ifExists=true',
+ 'status_code' => 200,
+ 'json_field_substr_match' => {'database' => 'default', 'table' => ':UNAME:_doastab2'},
+ },
+ {
+ # create a table and set permission so that it's only accessible by owner
+ #(i.e. user issuing request)
+ 'num' => 2,
+ 'method' => 'PUT',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab2?user.name=:UNAME:',
+ 'format_header' => 'Content-Type: application/json',
+ 'post_options' => [' {
+ "columns": [
+ { "name": "id", "type": "bigint" },
+ { "name": "price", "type": "float"} ],
+ "partitionedBy": [
+ { "name": "country", "type": "string" } ],
+ "format" : { "storedAs" : "textfile"},
+ "permissions" : "rwx------"
+ }'],
+ 'status_code' => 200,
+ 'json_field_substr_match' => {'database' => 'default', 'table' => ':UNAME:_doastab2'},
+ },
+ {
+ #describe table with doAs user that UNAME is not allowed to impersonate
+ 'num' => 3,
+ 'method' => 'GET',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab1?user.name=:UNAME:&doAs=no_such_user',
+ 'status_code' => 401,
+ 'json_field_substr_match' => {'error' => 'Unauthorized proxyuser \[:UNAME:\] for doAsUser \[no_such_user\], not in proxyuser groups'},
+ },
+ {
+ #try to describe tale as a user that does not own (doesn't have read permissions on ) the table
+ #this is not going to work in secure mode
+ 'ignore' => 'will not work in secure mode',
+ 'num' => 4,
+ 'method' => 'GET',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab1?user.name=no_such_user',
+ 'status_code' => 401,
+ 'json_field_substr_match' => {'error' => 'Unauthorized proxyuser \[:UNAME:\] for doAsUser \[no_such_user\], not in proxyuser groups'},
+ },
+
+ {
+ #descbe the table (as the table owner)
+ #this should succeed
+ 'num' => 5,
+ 'method' => 'GET',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab2?user.name=:UNAME:',
+ 'status_code' => 200,
+ 'json_field_substr_match' => {'database' => 'default', 'table' => ':UNAME:_doastab2'},
+ },
+
+ {
+ #descbe the table (as the table owner but using doAs)
+ #this should succeed (it seems reading metadata is allowed even if reading data is not)
+ 'num' => 6,
+ 'method' => 'GET',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab2/partition?user.name=:UNAME:?doAs=:DOAS:',
+ 'status_code' => 200,
+ 'json_field_substr_match' => {'database' => 'default', 'table' => ':UNAME:_doastab2'},
+ },
+
+ {
+ #this should fail
+ 'num' => 7,
+ 'method' => 'DELETE',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab2?user.name=:UNAME:&doAs=:DOAS:',
+ 'status_code' => 500,
+ 'json_field_substr_match' => {'error' => 'FAILED: Execution Error, return code 1 from org\.apache\.hadoop\.hive\.ql\.exec\.DDLTask\. MetaException\(message:java\.security\.AccessControlException: action WRITE not permitted on path.* for user :DOAS:\)'},
+ },
+ {
+ #descbe the table....
+ #this should succeed
+ 'num' => 8,
+ 'ignore' => 'foo',
+ 'method' => 'DELETE',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab2?user.name=:UNAME:',
+ 'status_code' => 200,
+ 'json_field_substr_match' => {'database' => 'default', 'table' => ':UNAME:_doastab2'},
+ },
+ ],
+ },
+ {
+ 'name' => 'bugs',
+ 'tests'=>
+ [
+ {
+ #update permissions to rwx------
+ 'num' => 1,
+ 'ignore' => 'permission setting seems broken - has no effect (not related to doAs) HIVE-5094',
+ 'method' => 'POST',
+ 'url' => ':TEMPLETON_URL:/templeton/v1/ddl/database/default/table/:UNAME:_doastab1?user.name=:UNAME:',
+ 'format_header' => 'Content-Type: application/json',
+ 'post_options' => ['rename=:UNAME:_doastab2', 'permissions=rwx------'],
+ 'status_code' => 200,
+ 'json_field_substr_match' => {'database' => 'default', 'table' => ':UNAME:_doastab2'},
+ },
+ ]
+ },
+ ]
+},
+ ;
+
Added: hive/trunk/hcatalog/webhcat/svr/src/main/java/org/apache/hcatalog/templeton/ProxyUserSupport.java
URL: http://svn.apache.org/viewvc/hive/trunk/hcatalog/webhcat/svr/src/main/java/org/apache/hcatalog/templeton/ProxyUserSupport.java?rev=1518912&view=auto
==============================================================================
--- hive/trunk/hcatalog/webhcat/svr/src/main/java/org/apache/hcatalog/templeton/ProxyUserSupport.java (added)
+++ hive/trunk/hcatalog/webhcat/svr/src/main/java/org/apache/hcatalog/templeton/ProxyUserSupport.java Fri Aug 30 08:23:27 2013
@@ -0,0 +1,241 @@
+/**
+ * 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.hcatalog.templeton;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.security.Groups;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * When WebHCat is run with doAs query parameter this class ensures that user making the
+ * call is allowed to impersonate doAs user and is making a call from authorized host.
+ */
+final class ProxyUserSupport {
+ private static final Log LOG = LogFactory.getLog(ProxyUserSupport.class);
+ private static final String CONF_PROXYUSER_PREFIX = "webhcat.proxyuser.";
+ private static final String CONF_GROUPS_SUFFIX = ".groups";
+ private static final String CONF_HOSTS_SUFFIX = ".hosts";
+ private static final Set<String> WILD_CARD = Collections.unmodifiableSet(new HashSet<String>(0));
+ private static final Map<String, Set<String>> proxyUserGroups = new HashMap<String, Set<String>>();
+ private static final Map<String, Set<String>> proxyUserHosts = new HashMap<String, Set<String>>();
+
+ static void processProxyuserConfig(AppConfig conf) {
+ for(Map.Entry<String, String> confEnt : conf) {
+ if(confEnt.getKey().startsWith(CONF_PROXYUSER_PREFIX)
+ && confEnt.getKey().endsWith(CONF_GROUPS_SUFFIX)) {
+ //process user groups for which doAs is authorized
+ String proxyUser =
+ confEnt.getKey().substring(CONF_PROXYUSER_PREFIX.length(),
+ confEnt.getKey().lastIndexOf(CONF_GROUPS_SUFFIX));
+ Set<String> groups;
+ if("*".equals(confEnt.getValue())) {
+ groups = WILD_CARD;
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("User [" + proxyUser + "] is authorized to do doAs any user.");
+ }
+ }
+ else if(confEnt.getValue() != null && confEnt.getValue().trim().length() > 0) {
+ groups = new HashSet<String>(Arrays.asList(confEnt.getValue().trim().split(",")));
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("User [" + proxyUser +
+ "] is authorized to do doAs for users in the following groups: ["
+ + confEnt.getValue().trim() + "]");
+ }
+ }
+ else {
+ groups = Collections.emptySet();
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("User [" + proxyUser +
+ "] is authorized to do doAs for users in the following groups: []");
+ }
+ }
+ proxyUserGroups.put(proxyUser, groups);
+ }
+ else if(confEnt.getKey().startsWith(CONF_PROXYUSER_PREFIX)
+ && confEnt.getKey().endsWith(CONF_HOSTS_SUFFIX)) {
+ //process hosts from which doAs requests are authorized
+ String proxyUser = confEnt.getKey().substring(CONF_PROXYUSER_PREFIX.length(),
+ confEnt.getKey().lastIndexOf(CONF_HOSTS_SUFFIX));
+ Set<String> hosts;
+ if("*".equals(confEnt.getValue())) {
+ hosts = WILD_CARD;
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("User [" + proxyUser + "] is authorized to do doAs from any host.");
+ }
+ }
+ else if(confEnt.getValue() != null && confEnt.getValue().trim().length() > 0) {
+ String[] hostValues = confEnt.getValue().trim().split(",");
+ hosts = new HashSet<String>();
+ for(String hostname : hostValues) {
+ String nhn = normalizeHostname(hostname);
+ if(nhn != null) {
+ hosts.add(nhn);
+ }
+ }
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("User [" + proxyUser +
+ "] is authorized to do doAs from the following hosts: ["
+ + confEnt.getValue().trim() + "]");
+ }
+ }
+ else {
+ hosts = Collections.emptySet();
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("User [" + proxyUser
+ + "] is authorized to do doAs from the following hosts: []");
+ }
+ }
+ proxyUserHosts.put(proxyUser, hosts);
+ }
+ }
+ }
+ /**
+ * Verifies a that proxyUser is making the request from authorized host and that doAs user
+ * belongs to one of the groups for which proxyUser is allowed to impersonate users.
+ *
+ * @param proxyUser user name of the proxy (logged in) user.
+ * @param proxyHost host the proxy user is making the request from.
+ * @param doAsUser user the proxy user is impersonating.
+ * @throws NotAuthorizedException thrown if the user is not allowed to perform the proxyuser request.
+ */
+ static void validate(String proxyUser, String proxyHost, String doAsUser) throws
+ NotAuthorizedException {
+ assertNotEmpty(proxyUser, "proxyUser",
+ "If you're attempting to use user-impersonation via a proxy user, please make sure that "
+ + CONF_PROXYUSER_PREFIX + "#USER#" + CONF_HOSTS_SUFFIX + " and "
+ + CONF_PROXYUSER_PREFIX + "#USER#" + CONF_GROUPS_SUFFIX
+ + " are configured correctly");
+ assertNotEmpty(proxyHost, "proxyHost",
+ "If you're attempting to use user-impersonation via a proxy user, please make sure that "
+ + CONF_PROXYUSER_PREFIX + proxyUser + CONF_HOSTS_SUFFIX + " and "
+ + CONF_PROXYUSER_PREFIX + proxyUser + CONF_GROUPS_SUFFIX
+ + " are configured correctly");
+ assertNotEmpty(doAsUser, Server.DO_AS_PARAM);
+ LOG.debug(MessageFormat.format("Authorization check proxyuser [{0}] host [{1}] doAs [{2}]",
+ proxyUser, proxyHost, doAsUser));
+ if (proxyUserHosts.containsKey(proxyUser)) {
+ proxyHost = normalizeHostname(proxyHost);
+ validateRequestorHost(proxyUser, proxyHost);
+ validateGroup(proxyUser, doAsUser);
+ }
+ else {
+ throw new NotAuthorizedException(MessageFormat.format(
+ "User [{0}] not defined as proxyuser", proxyUser));
+ }
+ }
+
+ private static void validateRequestorHost(String proxyUser, String hostname) throws
+ NotAuthorizedException {
+ Set<String> validHosts = proxyUserHosts.get(proxyUser);
+ if (validHosts == WILD_CARD) {
+ return;
+ }
+ if (validHosts == null || !validHosts.contains(hostname)) {
+ throw new NotAuthorizedException(MessageFormat.format(
+ "Unauthorized host [{0}] for proxyuser [{1}]", hostname, proxyUser));
+ }
+ }
+
+ private static void validateGroup(String proxyUser, String doAsUser) throws
+ NotAuthorizedException {
+ Set<String> validGroups = proxyUserGroups.get(proxyUser);
+ if(validGroups == WILD_CARD) {
+ return;
+ }
+ else if(validGroups == null || validGroups.isEmpty()) {
+ throw new NotAuthorizedException(
+ MessageFormat.format(
+ "Unauthorized proxyuser [{0}] for doAsUser [{1}], not in proxyuser groups",
+ proxyUser, doAsUser));
+ }
+ Groups groupsInfo = new Groups(Main.getAppConfigInstance());
+ try {
+ List<String> userGroups = groupsInfo.getGroups(doAsUser);
+ for (String g : validGroups) {
+ if (userGroups.contains(g)) {
+ return;
+ }
+ }
+ }
+ catch (IOException ex) {//thrown, for example, if there is no such user on the system
+ LOG.warn(MessageFormat.format("Unable to get list of groups for doAsUser [{0}].",
+ doAsUser), ex);
+ }
+ throw new NotAuthorizedException(
+ MessageFormat.format(
+ "Unauthorized proxyuser [{0}] for doAsUser [{1}], not in proxyuser groups",
+ proxyUser, doAsUser));
+ }
+
+ private static String normalizeHostname(String name) {
+ try {
+ InetAddress address = InetAddress.getByName(
+ "localhost".equalsIgnoreCase(name) ? null : name);
+ return address.getCanonicalHostName();
+ }
+ catch (UnknownHostException ex) {
+ LOG.warn(MessageFormat.format("Unable to normalize hostname [{0}]", name));
+ return null;
+ }
+ }
+ /**
+ * Check that a string is not null and not empty. If null or empty
+ * throws an IllegalArgumentException.
+ *
+ * @param str value.
+ * @param name parameter name for the exception message.
+ * @return the given value.
+ */
+ private static String assertNotEmpty(String str, String name) {
+ return assertNotEmpty(str, name, null);
+ }
+
+ /**
+ * Check that a string is not null and not empty. If null or empty
+ * throws an IllegalArgumentException.
+ *
+ * @param str value.
+ * @param name parameter name for the exception message.
+ * @param info additional information to be printed with the exception message
+ * @return the given value.
+ */
+ private static String assertNotEmpty(String str, String name, String info) {
+ if (str == null) {
+ throw new IllegalArgumentException(
+ name + " cannot be null" + (info == null ? "" : ", " + info));
+ }
+ if (str.length() == 0) {
+ throw new IllegalArgumentException(
+ name + " cannot be empty" + (info == null ? "" : ", " + info));
+ }
+ return str;
+ }
+}