Add kerberos and SSL support for client libraries

Signed-off-by: Sailaja Polavarapu <spolavarapu@cloudera.com>
diff --git a/intg/src/main/java/org/apache/ranger/RangerClient.java b/intg/src/main/java/org/apache/ranger/RangerClient.java
index 29b2ec0..0e2fe56 100644
--- a/intg/src/main/java/org/apache/ranger/RangerClient.java
+++ b/intg/src/main/java/org/apache/ranger/RangerClient.java
@@ -26,16 +26,21 @@
 import org.apache.ranger.admin.client.datatype.RESTResponse;
 import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
 import org.apache.ranger.plugin.util.RangerRESTClient;
+import org.apache.hadoop.security.SecureClientLogin;
 
+import javax.security.auth.Subject;
+import java.security.PrivilegedAction;
 import javax.ws.rs.HttpMethod;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.io.IOException;
 import java.net.URI;
 import java.util.*;
 
 
 public class RangerClient {
     private static final Logger LOG = LoggerFactory.getLogger(RangerClient.class);
+    private static final String AUTH_KERBEROS    = "kerberos";
 
     // QueryParams
     private static final String PARAM_DAYS                          = "days";
@@ -133,13 +138,33 @@
 
 
     private final RangerRESTClient restClient;
+    private boolean isSecureMode = false;
+    private Subject sub = null;
+
+    public RangerClient(String configFile) {
+        RangerClientConfig cfg = new RangerClientConfig(configFile);
+        restClient             = new RangerRESTClient(cfg.getURL(), cfg.getSslConfigFile(), new Configuration());
+
+        String authenticationType = cfg.getAuthenticationType();
+        String principal          = cfg.getPrincipal();
+        String keytab             = cfg.getKeytab();
+
+        if (AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal, keytab)) {
+            isSecureMode = true;
+            try {
+                sub = SecureClientLogin.loginUserFromKeytab(principal,keytab);
+            } catch (IOException e) {
+                LOG.error(e.getMessage());
+            }
+        } else LOG.error("Authentication credentials missing/invalid");
+    }
 
 
     public RangerClient(String hostname, String username, String password) {
         restClient = new RangerRESTClient(hostname, "", new Configuration());
 
         restClient.setBasicAuthInfo(username, password);
-    }
+   }
 
     public RangerClient(RangerRESTClient restClient) {
         this.restClient = restClient;
@@ -380,6 +405,36 @@
         callAPI(DELETE_POLICY_DELTAS, queryParams, null, null);
     }
 
+    private ClientResponse invokeREST(API api, Map<String, String> params, Object request) throws RangerServiceException {
+        final ClientResponse clientResponse;
+        try {
+            switch (api.getMethod()) {
+                case HttpMethod.POST:
+                    clientResponse = restClient.post(api.getPath(), params, request);
+                    break;
+
+                case HttpMethod.PUT:
+                    clientResponse = restClient.put(api.getPath(), params, request);
+                    break;
+
+                case HttpMethod.GET:
+                    clientResponse = restClient.get(api.getPath(), params);
+                    break;
+
+                case HttpMethod.DELETE:
+                    clientResponse = restClient.delete(api.getPath(), params);
+                    break;
+
+                default:
+                    LOG.error(api.getMethod() + ": unsupported HTTP method");
+
+                    clientResponse = null;
+            }
+        } catch (Exception excp) {
+            throw new RangerServiceException(excp);
+        }
+        return clientResponse;
+    }
 
     private <T> T callAPI(API api, Map<String, String> params, Object request, Class<T> responseType) throws RangerServiceException {
         T ret = null;
@@ -397,32 +452,16 @@
 
         final ClientResponse clientResponse;
 
-        try {
-            switch (api.getMethod()) {
-                case HttpMethod.POST:
-                    clientResponse = restClient.post(api.getPath(), params, request);
-                break;
-
-                case HttpMethod.PUT:
-                    clientResponse = restClient.put(api.getPath(), params, request);
-                break;
-
-                case HttpMethod.GET:
-                    clientResponse = restClient.get(api.getPath(), params);
-                break;
-
-                case HttpMethod.DELETE:
-                    clientResponse = restClient.delete(api.getPath(), params);
-                break;
-
-                default:
-                    LOG.error(api.getMethod() + ": unsupported HTTP method");
-
-                    clientResponse = null;
-            }
-        } catch (Exception excp) {
-            throw new RangerServiceException(excp);
-        }
+        if (isSecureMode) {
+            clientResponse = Subject.doAs(sub, (PrivilegedAction<ClientResponse>) () -> {
+                try {
+                    return invokeREST(api,params,request);
+                } catch (RangerServiceException e) {
+                    LOG.error(e.getMessage());
+                }
+                return null;
+            });
+        } else clientResponse = invokeREST(api,params,request);
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("method={}, path={}, contentType={}, accept={}, httpStatus={}", api.getMethod(), api.getNormalizedPath(), api.getConsumes(), api.getProduces(), (clientResponse != null ? clientResponse.getStatus() : "null"));
diff --git a/intg/src/main/java/org/apache/ranger/RangerClientConfig.java b/intg/src/main/java/org/apache/ranger/RangerClientConfig.java
new file mode 100644
index 0000000..68ef0ff
--- /dev/null
+++ b/intg/src/main/java/org/apache/ranger/RangerClientConfig.java
@@ -0,0 +1,119 @@
+/*
+ * 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.ranger;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+public class RangerClientConfig  {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RangerClientConfig.class);
+
+    private static final String RANGER_ADMIN_URL          = "ranger.client.url";
+    private static final String AUTH_TYPE                 = "ranger.client.authentication.type";
+    private static final String CLIENT_KERBEROS_PRINCIPAL = "ranger.client.kerberos.principal";
+    private static final String CLIENT_KERBEROS_KEYTAB    = "ranger.client.kerberos.keytab";
+    private static final String CLIENT_SSL_CONFIG_FILE    = "ranger.client.ssl.config.filename";
+
+
+    private final Properties props;
+
+    RangerClientConfig(String configFileName){
+        props = readProperties(configFileName);
+    }
+
+    public Properties readProperties(String fileName) {
+        Properties  ret     = null;
+        InputStream inStr   = null;
+        URL         fileURL = null;
+        File        f       = new File(fileName);
+
+        if (f.exists() && f.isFile() && f.canRead()) {
+            try {
+                inStr   = new FileInputStream(f);
+                fileURL = f.toURI().toURL();
+            } catch (FileNotFoundException exception) {
+                LOG.error("Error processing input file:" + fileName + " or no privilege for reading file " + fileName, exception);
+            } catch (MalformedURLException malformedException) {
+                LOG.error("Error processing input file:" + fileName + " cannot be converted to URL " + fileName, malformedException);
+            }
+        } else {
+            fileURL = getClass().getResource(fileName);
+
+            if (fileURL == null && !fileName.startsWith("/")) {
+                fileURL = getClass().getResource("/" + fileName);
+            }
+
+            if (fileURL == null) {
+                fileURL = ClassLoader.getSystemClassLoader().getResource(fileName);
+
+                if (fileURL == null && !fileName.startsWith("/")) {
+                    fileURL = ClassLoader.getSystemClassLoader().getResource("/" + fileName);
+                }
+            }
+        }
+
+        if (fileURL != null) {
+            try {
+                inStr = fileURL.openStream();
+
+                Properties prop = new Properties();
+
+                prop.load(inStr);
+
+                ret = prop;
+            } catch (Exception excp) {
+                LOG.error("failed to load properties from file '" + fileName + "'", excp);
+            } finally {
+                if (inStr != null) {
+                    try {
+                        inStr.close();
+                    } catch (Exception excp) {
+                        // ignore
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+    public String getURL() { return props.getProperty(RANGER_ADMIN_URL); }
+
+    public String getPrincipal(){
+        return props.getProperty(CLIENT_KERBEROS_PRINCIPAL);
+    }
+
+    public String getKeytab(){
+        return props.getProperty(CLIENT_KERBEROS_KEYTAB);
+    }
+
+    public String getSslConfigFile(){
+        return props.getProperty(CLIENT_SSL_CONFIG_FILE);
+    }
+
+    public String getAuthenticationType(){
+        return props.getProperty(AUTH_TYPE);
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index adac120..bc887ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -291,6 +291,7 @@
             <id>ranger-examples</id>
             <modules>
                 <module>agents-common</module>
+                <module>agents-cred</module>
                 <module>intg</module>
                 <module>ranger-examples</module>
             </modules>
diff --git a/ranger-examples/distro/src/main/assembly/sample-client.xml b/ranger-examples/distro/src/main/assembly/sample-client.xml
index ea915a6..cde39f1 100644
--- a/ranger-examples/distro/src/main/assembly/sample-client.xml
+++ b/ranger-examples/distro/src/main/assembly/sample-client.xml
@@ -29,6 +29,7 @@
                 <include>org.apache.ranger:sample-client</include>
                 <include>org.apache.ranger:ranger-intg</include>
                 <include>org.apache.ranger:ranger-plugins-common</include>
+                <include>org.apache.ranger:ranger-plugins-cred</include>
             </includes>
             <binaries>
                 <outputDirectory>lib</outputDirectory>
@@ -60,6 +61,7 @@
                     <include>org.codehaus.jackson:jackson-mapper-asl</include>
                     <include>org.codehaus.jackson:jackson-xc</include>
                     <include>org.apache.ranger:ranger-plugins-audit</include>
+                    <include>org.apache.htrace:htrace-core4</include>
                     <include>com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}</include>
                     <include>net.java.dev.jna:jna:jar:${jna.version}</include>
                     <include>net.java.dev.jna:jna-platform:jar:${jna-platform.version}</include>
@@ -79,6 +81,15 @@
             </includes>
             <fileMode>755</fileMode>
         </fileSet>
+<!--        only for testing -->
+        <fileSet>
+            <outputDirectory></outputDirectory>
+            <directory>${project.parent.basedir}/sample-client/conf</directory>
+            <includes>
+                <include>*.properties</include>
+            </includes>
+            <fileMode>755</fileMode>
+        </fileSet>
         <fileSet>
             <directoryMode>755</directoryMode>
             <fileMode>644</fileMode>