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.io.IOException;
021import java.util.Arrays;
022import java.util.Base64;
023import java.util.List;
024import java.util.Map;
025import java.util.TreeMap;
026import javax.security.sasl.Sasl;
027import javax.security.sasl.SaslClient;
028import javax.security.sasl.SaslException;
029import javax.security.sasl.SaslServer;
030import org.apache.hadoop.hbase.util.Bytes;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
036
037@InterfaceAudience.Private
038public class SaslUtil {
039  private static final Logger LOG = LoggerFactory.getLogger(SaslUtil.class);
040  public static final String SASL_DEFAULT_REALM = "default";
041  public static final int SWITCH_TO_SIMPLE_AUTH = -88;
042
043  public enum QualityOfProtection {
044    AUTHENTICATION("auth"),
045    INTEGRITY("auth-int"),
046    PRIVACY("auth-conf");
047
048    private final String saslQop;
049
050    QualityOfProtection(String saslQop) {
051      this.saslQop = saslQop;
052    }
053
054    public String getSaslQop() {
055      return saslQop;
056    }
057
058    public boolean matches(String stringQop) {
059      if (saslQop.equals(stringQop)) {
060        LOG.warn("Use authentication/integrity/privacy as value for rpc protection "
061          + "configurations instead of auth/auth-int/auth-conf.");
062        return true;
063      }
064      return name().equalsIgnoreCase(stringQop);
065    }
066  }
067
068  /** Splitting fully qualified Kerberos name into parts */
069  public static String[] splitKerberosName(String fullName) {
070    return Splitter.onPattern("[/@]").splitToStream(fullName).toArray(String[]::new);
071  }
072
073  public static String encodeIdentifier(byte[] identifier) {
074    return Base64.getEncoder().encodeToString(identifier);
075  }
076
077  public static byte[] decodeIdentifier(String identifier) {
078    return Base64.getDecoder().decode(Bytes.toBytes(identifier));
079  }
080
081  public static char[] encodePassword(byte[] password) {
082    return Base64.getEncoder().encodeToString(password).toCharArray();
083  }
084
085  /**
086   * Returns {@link org.apache.hadoop.hbase.security.SaslUtil.QualityOfProtection} corresponding to
087   * the given {@code stringQop} value.
088   * @throws IllegalArgumentException If stringQop doesn't match any QOP.
089   */
090  public static QualityOfProtection getQop(String stringQop) {
091    for (QualityOfProtection qop : QualityOfProtection.values()) {
092      if (qop.matches(stringQop)) {
093        return qop;
094      }
095    }
096    throw new IllegalArgumentException("Invalid qop: " + stringQop
097      + ". It must be one of 'authentication', 'integrity', 'privacy'.");
098  }
099
100  /**
101   * Initialize SASL properties for a given RPC protection level.
102   * @param rpcProtection Value of 'hbase.rpc.protection' configuration.
103   * @return Map with values for SASL properties.
104   */
105  public static Map<String, String> initSaslProperties(String rpcProtection) {
106    String saslQop;
107    if (rpcProtection.isEmpty()) {
108      saslQop = QualityOfProtection.AUTHENTICATION.getSaslQop();
109    } else {
110      StringBuilder saslQopBuilder = new StringBuilder();
111      for (String s : Splitter.on(',').split(rpcProtection)) {
112        QualityOfProtection qop = getQop(s);
113        saslQopBuilder.append(",").append(qop.getSaslQop());
114      }
115      saslQop = saslQopBuilder.substring(1); // remove first ','
116    }
117    Map<String, String> saslProps = new TreeMap<>();
118    saslProps.put(Sasl.QOP, saslQop);
119    saslProps.put(Sasl.SERVER_AUTH, "true");
120    return saslProps;
121  }
122
123  static void safeDispose(SaslClient saslClient) {
124    try {
125      saslClient.dispose();
126    } catch (SaslException e) {
127      LOG.error("Error disposing of SASL client", e);
128    }
129  }
130
131  static void safeDispose(SaslServer saslServer) {
132    try {
133      saslServer.dispose();
134    } catch (SaslException e) {
135      LOG.error("Error disposing of SASL server", e);
136    }
137  }
138
139  public static void verifyNegotiatedQop(String requestedQopString, String negotiatedQop)
140    throws IOException {
141    // We use the SASL QOP names here, not the HBase names
142    if (requestedQopString == null || requestedQopString.isEmpty()) {
143      // None requested, nothing to check
144      return;
145    }
146    List<String> requestedQops = Arrays.asList(requestedQopString.toLowerCase().split(","));
147    if (negotiatedQop == null) {
148      // Null negotiated QOP is equivalent to "auth" (for mechanisms without QOP support)
149      negotiatedQop = QualityOfProtection.AUTHENTICATION.getSaslQop();
150    }
151    if (requestedQops.contains(negotiatedQop.toLowerCase())) {
152      return;
153    }
154    throw new IOException("Could not negotiate requested SASL QOP. Requested:" + requestedQopString
155      + " , negotiated:" + negotiatedQop);
156  }
157}