001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security;
019
020import java.util.Base64;
021import java.util.Map;
022import java.util.TreeMap;
023import javax.security.sasl.Sasl;
024import javax.security.sasl.SaslClient;
025import javax.security.sasl.SaslException;
026import javax.security.sasl.SaslServer;
027import org.apache.hadoop.hbase.util.Bytes;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
033
034@InterfaceAudience.Private
035public class SaslUtil {
036  private static final Logger LOG = LoggerFactory.getLogger(SaslUtil.class);
037  public static final String SASL_DEFAULT_REALM = "default";
038  public static final int SWITCH_TO_SIMPLE_AUTH = -88;
039
040  public enum QualityOfProtection {
041    AUTHENTICATION("auth"),
042    INTEGRITY("auth-int"),
043    PRIVACY("auth-conf");
044
045    private final String saslQop;
046
047    QualityOfProtection(String saslQop) {
048      this.saslQop = saslQop;
049    }
050
051    public String getSaslQop() {
052      return saslQop;
053    }
054
055    public boolean matches(String stringQop) {
056      if (saslQop.equals(stringQop)) {
057        LOG.warn("Use authentication/integrity/privacy as value for rpc protection "
058          + "configurations instead of auth/auth-int/auth-conf.");
059        return true;
060      }
061      return name().equalsIgnoreCase(stringQop);
062    }
063  }
064
065  /** Splitting fully qualified Kerberos name into parts */
066  public static String[] splitKerberosName(String fullName) {
067    return Splitter.onPattern("[/@]").splitToStream(fullName).toArray(String[]::new);
068  }
069
070  public static String encodeIdentifier(byte[] identifier) {
071    return Base64.getEncoder().encodeToString(identifier);
072  }
073
074  public static byte[] decodeIdentifier(String identifier) {
075    return Base64.getDecoder().decode(Bytes.toBytes(identifier));
076  }
077
078  public static char[] encodePassword(byte[] password) {
079    return Base64.getEncoder().encodeToString(password).toCharArray();
080  }
081
082  /**
083   * Returns {@link org.apache.hadoop.hbase.security.SaslUtil.QualityOfProtection} corresponding to
084   * the given {@code stringQop} value.
085   * @throws IllegalArgumentException If stringQop doesn't match any QOP.
086   */
087  public static QualityOfProtection getQop(String stringQop) {
088    for (QualityOfProtection qop : QualityOfProtection.values()) {
089      if (qop.matches(stringQop)) {
090        return qop;
091      }
092    }
093    throw new IllegalArgumentException("Invalid qop: " + stringQop
094      + ". It must be one of 'authentication', 'integrity', 'privacy'.");
095  }
096
097  /**
098   * Initialize SASL properties for a given RPC protection level.
099   * @param rpcProtection Value of 'hbase.rpc.protection' configuration.
100   * @return Map with values for SASL properties.
101   */
102  public static Map<String, String> initSaslProperties(String rpcProtection) {
103    String saslQop;
104    if (rpcProtection.isEmpty()) {
105      saslQop = QualityOfProtection.AUTHENTICATION.getSaslQop();
106    } else {
107      StringBuilder saslQopBuilder = new StringBuilder();
108      for (String s : Splitter.on(',').split(rpcProtection)) {
109        QualityOfProtection qop = getQop(s);
110        saslQopBuilder.append(",").append(qop.getSaslQop());
111      }
112      saslQop = saslQopBuilder.substring(1); // remove first ','
113    }
114    Map<String, String> saslProps = new TreeMap<>();
115    saslProps.put(Sasl.QOP, saslQop);
116    saslProps.put(Sasl.SERVER_AUTH, "true");
117    return saslProps;
118  }
119
120  static void safeDispose(SaslClient saslClient) {
121    try {
122      saslClient.dispose();
123    } catch (SaslException e) {
124      LOG.error("Error disposing of SASL client", e);
125    }
126  }
127
128  static void safeDispose(SaslServer saslServer) {
129    try {
130      saslServer.dispose();
131    } catch (SaslException e) {
132      LOG.error("Error disposing of SASL server", e);
133    }
134  }
135}