org.apache.ranger.services.kms.client.KMSClient.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ranger.services.kms.client.KMSClient.java

Source

/*
 * 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.services.kms.client;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.HadoopKerberosName;
import org.apache.hadoop.security.ProviderUtils;
import org.apache.hadoop.security.SecureClientLogin;
import org.apache.log4j.Logger;
import org.apache.ranger.plugin.client.BaseClient;
import org.apache.ranger.plugin.util.PasswordUtils;
import org.apache.ranger.plugin.client.HadoopException;
import org.apache.ranger.services.kms.client.KMSClient;

import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;

public class KMSClient {

    private static final Logger LOG = Logger.getLogger(KMSClient.class);

    private static final String EXPECTED_MIME_TYPE = "application/json";

    private static final String KMS_LIST_API_ENDPOINT = "v1/keys/names"; // GET

    private static final String errMessage = " You can still save the repository and start creating "
            + "policies, but you would not be able to use autocomplete for "
            + "resource names. Check ranger_admin.log for more info.";

    private static final String AUTH_TYPE_KERBEROS = "kerberos";

    String provider;
    String username;
    String password;
    String rangerPrincipal;
    String rangerKeytab;
    String nameRules;
    String authType;

    public KMSClient(String provider, String username, String password, String rangerPrincipal, String rangerKeytab,
            String nameRules, String authType) {
        this.provider = provider;
        this.username = username;
        this.password = password;
        this.rangerPrincipal = rangerPrincipal;
        this.rangerKeytab = rangerKeytab;
        this.nameRules = nameRules;
        this.authType = authType;

        if (LOG.isDebugEnabled()) {
            LOG.debug("Kms Client is build with url [" + provider + "] user: [" + username + "]");
        }
    }

    private String[] createProvider(String uri) throws IOException, URISyntaxException {
        URI providerUri = new URI(uri);
        URL origUrl = new URL(extractKMSPath(providerUri).toString());
        String authority = origUrl.getAuthority();
        // check for ';' which delimits the backup hosts
        if (Strings.isNullOrEmpty(authority)) {
            throw new IOException("No valid authority in kms uri [" + origUrl + "]");
        }
        // Check if port is present in authority
        // In the current scheme, all hosts have to run on the same port
        int port = -1;
        String hostsPart = authority;
        if (authority.contains(":")) {
            String[] t = authority.split(":");
            try {
                port = Integer.parseInt(t[1]);
            } catch (Exception e) {
                throw new IOException("Could not parse port in kms uri [" + origUrl + "]");
            }
            hostsPart = t[0];
        }
        return createProvider(providerUri, origUrl, port, hostsPart);
    }

    private static Path extractKMSPath(URI uri) throws MalformedURLException, IOException {
        return ProviderUtils.unnestUri(uri);
    }

    private String[] createProvider(URI providerUri, URL origUrl, int port, String hostsPart) throws IOException {
        String[] hosts = hostsPart.split(";");
        String[] providers = new String[hosts.length];
        if (hosts.length == 1) {
            providers[0] = origUrl.toString();
        } else {
            for (int i = 0; i < hosts.length; i++) {
                try {
                    String url = origUrl.getProtocol() + "://" + hosts[i] + ":" + port + origUrl.getPath();
                    providers[i] = new URI(url).toString();
                } catch (URISyntaxException e) {
                    throw new IOException("Could not Prase KMS URL..", e);
                }
            }
        }
        return providers;
    }

    public List<String> getKeyList(final String keyNameMatching, final List<String> existingKeyList) {

        String providers[] = null;
        try {
            providers = createProvider(provider);
        } catch (IOException | URISyntaxException e) {
            return null;
        }
        final String errMsg = errMessage;
        List<String> lret = null;
        for (int i = 0; i < providers.length; i++) {
            lret = new ArrayList<String>();
            if (LOG.isDebugEnabled()) {
                LOG.debug("Getting Kms Key list for keyNameMatching : " + keyNameMatching);
            }
            String uri = providers[i]
                    + (providers[i].endsWith("/") ? KMS_LIST_API_ENDPOINT : ("/" + KMS_LIST_API_ENDPOINT));
            Client client = null;
            ClientResponse response = null;
            boolean isKerberos = false;
            try {
                ClientConfig cc = new DefaultClientConfig();
                cc.getProperties().put(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, true);
                client = Client.create(cc);

                if (authType != null && authType.equalsIgnoreCase(AUTH_TYPE_KERBEROS)) {
                    isKerberos = true;
                }

                Subject sub = new Subject();
                if (!isKerberos) {
                    uri = uri.concat("?user.name=" + username);
                    WebResource webResource = client.resource(uri);
                    response = webResource.accept(EXPECTED_MIME_TYPE).get(ClientResponse.class);
                    LOG.info("Init Login: security not enabled, using username");
                    sub = SecureClientLogin.login(username);
                } else {
                    if (!StringUtils.isEmpty(rangerPrincipal) && !StringUtils.isEmpty(rangerKeytab)) {
                        LOG.info("Init Lookup Login: security enabled, using rangerPrincipal/rangerKeytab");
                        if (StringUtils.isEmpty(nameRules)) {
                            nameRules = "DEFAULT";
                        }
                        String shortName = new HadoopKerberosName(rangerPrincipal).getShortName();
                        uri = uri.concat("?doAs=" + shortName);
                        sub = SecureClientLogin.loginUserFromKeytab(rangerPrincipal, rangerKeytab, nameRules);
                    } else {
                        LOG.info("Init Login: using username/password");
                        String shortName = new HadoopKerberosName(username).getShortName();
                        uri = uri.concat("?doAs=" + shortName);
                        String decryptedPwd = PasswordUtils.decryptPassword(password);
                        sub = SecureClientLogin.loginUserWithPassword(username, decryptedPwd);
                    }
                }
                final WebResource webResource = client.resource(uri);
                response = Subject.doAs(sub, new PrivilegedAction<ClientResponse>() {
                    @Override
                    public ClientResponse run() {
                        return webResource.accept(EXPECTED_MIME_TYPE).get(ClientResponse.class);
                    }
                });

                if (LOG.isDebugEnabled()) {
                    LOG.debug("getKeyList():calling " + uri);
                }
                if (response != null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("getKeyList():response.getStatus()= " + response.getStatus());
                    }
                    if (response.getStatus() == 200) {
                        String jsonString = response.getEntity(String.class);
                        Gson gson = new GsonBuilder().setPrettyPrinting().create();
                        @SuppressWarnings("unchecked")
                        List<String> keys = gson.fromJson(jsonString, List.class);
                        if (keys != null) {
                            for (String key : keys) {
                                if (existingKeyList != null && existingKeyList.contains(key)) {
                                    continue;
                                }
                                if (keyNameMatching == null || keyNameMatching.isEmpty()
                                        || key.startsWith(keyNameMatching)) {
                                    if (LOG.isDebugEnabled()) {
                                        LOG.debug("getKeyList():Adding kmsKey " + key);
                                    }
                                    lret.add(key);
                                }
                            }
                            return lret;
                        }
                    } else if (response.getStatus() == 401) {
                        LOG.info("getKeyList():response.getStatus()= " + response.getStatus() + " for URL " + uri
                                + ", so returning null list");
                        String msgDesc = response.getEntity(String.class);
                        HadoopException hdpException = new HadoopException(msgDesc);
                        hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null);
                        lret = null;
                        throw hdpException;
                    } else if (response.getStatus() == 403) {
                        LOG.info("getKeyList():response.getStatus()= " + response.getStatus() + " for URL " + uri
                                + ", so returning null list");
                        String msgDesc = response.getEntity(String.class);
                        HadoopException hdpException = new HadoopException(msgDesc);
                        hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null);
                        lret = null;
                        throw hdpException;
                    } else {
                        LOG.info("getKeyList():response.getStatus()= " + response.getStatus() + " for URL " + uri
                                + ", so returning null list");
                        String jsonString = response.getEntity(String.class);
                        LOG.info(jsonString);
                        lret = null;
                    }
                } else {
                    String msgDesc = "Unable to get a valid response for " + "expected mime type : ["
                            + EXPECTED_MIME_TYPE + "] URL : " + uri + " - got null response.";
                    LOG.error(msgDesc);
                    HadoopException hdpException = new HadoopException(msgDesc);
                    hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null);
                    lret = null;
                    throw hdpException;
                }
            } catch (HadoopException he) {
                lret = null;
                throw he;
            } catch (Throwable t) {
                String msgDesc = "Exception while getting Kms Key List. URL : " + uri;
                HadoopException hdpException = new HadoopException(msgDesc, t);
                LOG.error(msgDesc, t);
                hdpException.generateResponseDataMap(false, BaseClient.getMessage(t), msgDesc + errMsg, null, null);
                lret = null;
                throw hdpException;
            } finally {
                if (response != null) {
                    response.close();
                }

                if (client != null) {
                    client.destroy();
                }

                if (lret == null) {
                    if (i != providers.length - 1)
                        continue;
                }
            }
        }
        return lret;
    }

    public static HashMap<String, Object> testConnection(String serviceName, Map<String, String> configs) {

        List<String> strList = new ArrayList<String>();
        String errMsg = errMessage;
        boolean connectivityStatus = false;
        HashMap<String, Object> responseData = new HashMap<String, Object>();

        KMSClient kmsClient = getKmsClient(serviceName, configs);
        strList = getKmsKey(kmsClient, "", null);
        if (strList != null) {
            connectivityStatus = true;
        }
        if (connectivityStatus) {
            String successMsg = "TestConnection Successful";
            BaseClient.generateResponseDataMap(connectivityStatus, successMsg, successMsg, null, null,
                    responseData);
        } else {
            String failureMsg = "Unable to retrieve any Kms Key using given URL.";
            BaseClient.generateResponseDataMap(connectivityStatus, failureMsg, failureMsg + errMsg, null, null,
                    responseData);
        }

        return responseData;
    }

    public static KMSClient getKmsClient(String serviceName, Map<String, String> configs) {
        KMSClient kmsClient = null;
        if (LOG.isDebugEnabled()) {
            LOG.debug("Getting KmsClient for datasource: " + serviceName);
            LOG.debug("configMap: " + configs);
        }
        String errMsg = errMessage;
        if (configs == null || configs.isEmpty()) {
            String msgDesc = "Could not connect as Connection ConfigMap is empty.";
            LOG.error(msgDesc);
            HadoopException hdpException = new HadoopException(msgDesc);
            hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null);
            throw hdpException;
        } else {
            String kmsUrl = configs.get("provider");
            String kmsUserName = configs.get("username");
            String kmsPassWord = configs.get("password");
            String rangerPrincipal = configs.get("rangerprincipal");
            String rangerKeytab = configs.get("rangerkeytab");
            String nameRules = configs.get("namerules");
            String authType = configs.get("authtype");

            kmsClient = new KMSClient(kmsUrl, kmsUserName, kmsPassWord, rangerPrincipal, rangerKeytab, nameRules,
                    authType);

        }
        return kmsClient;
    }

    public static List<String> getKmsKey(final KMSClient kmsClient, String keyName, List<String> existingKeyName) {

        List<String> resultList = new ArrayList<String>();
        String errMsg = errMessage;

        try {
            if (kmsClient == null) {
                String msgDesc = "Unable to get Kms Key : KmsClient is null.";
                LOG.error(msgDesc);
                HadoopException hdpException = new HadoopException(msgDesc);
                hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null);
                throw hdpException;
            }

            if (keyName != null) {
                String finalkmsKeyName = keyName.trim();
                resultList = kmsClient.getKeyList(finalkmsKeyName, existingKeyName);
                if (resultList != null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Returning list of " + resultList.size() + " Kms Keys");
                    }
                }
            }
        } catch (HadoopException he) {
            resultList = null;
            throw he;
        } catch (Exception e) {
            String msgDesc = "Unable to get a valid response from the provider : " + e.getMessage();
            LOG.error(msgDesc, e);
            HadoopException hdpException = new HadoopException(msgDesc);
            hdpException.generateResponseDataMap(false, msgDesc, msgDesc + errMsg, null, null);
            resultList = null;
            throw hdpException;
        }
        return resultList;
    }
}