org.apache.accumulo.core.rpc.SaslConnectionParams.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.accumulo.core.rpc.SaslConnectionParams.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.accumulo.core.rpc;

import static java.util.Objects.requireNonNull;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;

import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.Sasl;

import org.apache.accumulo.core.client.ClientConfiguration;
import org.apache.accumulo.core.client.ClientConfiguration.ClientProperty;
import org.apache.accumulo.core.client.impl.DelegationTokenImpl;
import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
import org.apache.accumulo.core.client.security.tokens.KerberosToken;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.Property;
import org.apache.commons.configuration.MapConfiguration;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.hadoop.security.authentication.util.KerberosUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Connection parameters for setting up a TSaslTransportFactory
 */
public class SaslConnectionParams {
    private static final Logger log = LoggerFactory.getLogger(SaslConnectionParams.class);

    /**
     * Enumeration around {@link Sasl#QOP}
     */
    public enum QualityOfProtection {
        AUTH("auth"), AUTH_INT("auth-int"), AUTH_CONF("auth-conf");

        private final String quality;

        private QualityOfProtection(String quality) {
            this.quality = quality;
        }

        public String getQuality() {
            return quality;
        }

        public static QualityOfProtection get(String name) {
            if (AUTH.quality.equals(name)) {
                return AUTH;
            } else if (AUTH_INT.quality.equals(name)) {
                return AUTH_INT;
            } else if (AUTH_CONF.quality.equals(name)) {
                return AUTH_CONF;
            }

            throw new IllegalArgumentException("No value for " + name);
        }

        @Override
        public String toString() {
            return quality;
        }
    }

    /**
     * The SASL mechanism to use for authentication
     */
    public enum SaslMechanism {
        GSSAPI("GSSAPI"), // Kerberos
        DIGEST_MD5("DIGEST-MD5"); // Delegation Tokens

        private final String mechanismName;

        private SaslMechanism(String mechanismName) {
            this.mechanismName = mechanismName;
        }

        public String getMechanismName() {
            return mechanismName;
        }

        public static SaslMechanism get(String mechanismName) {
            if (GSSAPI.mechanismName.equals(mechanismName)) {
                return GSSAPI;
            } else if (DIGEST_MD5.mechanismName.equals(mechanismName)) {
                return DIGEST_MD5;
            }

            throw new IllegalArgumentException("No value for " + mechanismName);
        }
    }

    private static String defaultRealm;

    static {
        try {
            defaultRealm = KerberosUtil.getDefaultRealm();
        } catch (Exception ke) {
            log.debug("Kerberos krb5 configuration not found, setting default realm to empty");
            defaultRealm = "UNKNOWN";
        }
    }

    protected String principal;
    protected QualityOfProtection qop;
    protected String kerberosServerPrimary;
    protected SaslMechanism mechanism;
    protected CallbackHandler callbackHandler;
    protected final Map<String, String> saslProperties;

    public SaslConnectionParams(AccumuloConfiguration conf, AuthenticationToken token) {
        this(new ClientConfiguration(createMapConfiguration(conf)), token);
    }

    private static MapConfiguration createMapConfiguration(AccumuloConfiguration conf) {
        MapConfiguration mapConf = new MapConfiguration(getProperties(conf));
        mapConf.setListDelimiter('\0');
        return mapConf;
    }

    public SaslConnectionParams(ClientConfiguration conf, AuthenticationToken token) {
        requireNonNull(conf, "Configuration was null");
        requireNonNull(token, "AuthenticationToken was null");

        saslProperties = new HashMap<>();
        updatePrincipalFromUgi();
        updateFromConfiguration(conf);
        updateFromToken(token);
    }

    protected void updateFromToken(AuthenticationToken token) {
        if (token instanceof KerberosToken) {
            mechanism = SaslMechanism.GSSAPI;
            // No callbackhandlers necessary for GSSAPI
            callbackHandler = null;
        } else if (token instanceof DelegationTokenImpl) {
            mechanism = SaslMechanism.DIGEST_MD5;
            callbackHandler = new SaslClientDigestCallbackHandler((DelegationTokenImpl) token);
        } else {
            throw new IllegalArgumentException(
                    "Cannot determine SASL mechanism for token class: " + token.getClass());
        }
    }

    protected static Map<String, String> getProperties(AccumuloConfiguration conf) {
        final Map<String, String> clientProperties = new HashMap<>();

        // Servers will only have the full principal in their configuration -- parse the
        // primary and realm from it.
        final String serverPrincipal = conf.get(Property.GENERAL_KERBEROS_PRINCIPAL);

        final KerberosName krbName;
        try {
            krbName = new KerberosName(serverPrincipal);
            clientProperties.put(ClientProperty.KERBEROS_SERVER_PRIMARY.getKey(), krbName.getServiceName());
        } catch (Exception e) {
            // bad value or empty, assume we're not using kerberos
        }

        HashSet<String> clientKeys = new HashSet<>();
        for (ClientProperty prop : ClientProperty.values()) {
            clientKeys.add(prop.getKey());
        }

        String key;
        for (Entry<String, String> entry : conf) {
            key = entry.getKey();
            if (clientKeys.contains(key)) {
                clientProperties.put(key, entry.getValue());
            }
        }

        return clientProperties;
    }

    protected void updatePrincipalFromUgi() {
        // Ensure we're using Kerberos auth for Hadoop UGI
        if (!UserGroupInformation.isSecurityEnabled()) {
            throw new RuntimeException("Cannot use SASL if Hadoop security is not enabled");
        }

        // Get the current user
        UserGroupInformation currentUser;
        try {
            currentUser = UserGroupInformation.getCurrentUser();
        } catch (IOException e) {
            throw new RuntimeException("Failed to get current user", e);
        }

        // The full name is our principal
        this.principal = currentUser.getUserName();
        if (null == this.principal) {
            throw new RuntimeException("Got null username from " + currentUser);
        }

    }

    protected void updateFromConfiguration(ClientConfiguration conf) {
        // Get the quality of protection to use
        final String qopValue = conf.get(ClientProperty.RPC_SASL_QOP);
        this.qop = QualityOfProtection.get(qopValue);

        // Add in the SASL properties to a map so we don't have to repeatedly construct this map
        this.saslProperties.put(Sasl.QOP, this.qop.getQuality());

        // The primary from the KRB principal on each server (e.g. primary/instance@realm)
        this.kerberosServerPrimary = conf.get(ClientProperty.KERBEROS_SERVER_PRIMARY);
    }

    public Map<String, String> getSaslProperties() {
        return Collections.unmodifiableMap(saslProperties);
    }

    /**
     * The quality of protection used with SASL. See {@link Sasl#QOP} for more information.
     */
    public QualityOfProtection getQualityOfProtection() {
        return qop;
    }

    /**
     * The 'primary' component from the Kerberos principals that servers are configured to use.
     */
    public String getKerberosServerPrimary() {
        return kerberosServerPrimary;
    }

    /**
     * The principal of the logged in user for SASL
     */
    public String getPrincipal() {
        return principal;
    }

    /**
     * The SASL mechanism to use for authentication
     */
    public SaslMechanism getMechanism() {
        return mechanism;
    }

    /**
     * The SASL callback handler for this mechanism, may be null.
     */
    public CallbackHandler getCallbackHandler() {
        return callbackHandler;
    }

    @Override
    public int hashCode() {
        HashCodeBuilder hcb = new HashCodeBuilder(23, 29);
        hcb.append(kerberosServerPrimary).append(saslProperties).append(qop.hashCode()).append(principal)
                .append(mechanism).append(callbackHandler);
        return hcb.toHashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof SaslConnectionParams) {
            SaslConnectionParams other = (SaslConnectionParams) o;
            if (!kerberosServerPrimary.equals(other.kerberosServerPrimary)) {
                return false;
            }
            if (qop != other.qop) {
                return false;
            }
            if (!principal.equals(other.principal)) {
                return false;
            }
            if (!mechanism.equals(other.mechanism)) {
                return false;
            }
            if (null == callbackHandler) {
                if (null != other.callbackHandler) {
                    return false;
                }
            } else if (!callbackHandler.equals(other.callbackHandler)) {
                return false;
            }

            return saslProperties.equals(other.saslProperties);
        }

        return false;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(64);
        sb.append("SaslConnectionParams[").append("kerberosServerPrimary=").append(kerberosServerPrimary)
                .append(", qualityOfProtection=").append(qop);
        sb.append(", principal=").append(principal).append(", mechanism=").append(mechanism)
                .append(", callbackHandler=").append(callbackHandler).append("]");
        return sb.toString();
    }

    public static String getDefaultRealm() {
        return defaultRealm;
    }
}