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}