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;
+    }
+}