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.provider; 019 020import java.io.IOException; 021import java.util.Map; 022import java.util.concurrent.atomic.AtomicReference; 023import javax.security.auth.callback.Callback; 024import javax.security.auth.callback.CallbackHandler; 025import javax.security.auth.callback.NameCallback; 026import javax.security.auth.callback.PasswordCallback; 027import javax.security.auth.callback.UnsupportedCallbackException; 028import javax.security.sasl.AuthorizeCallback; 029import javax.security.sasl.RealmCallback; 030import javax.security.sasl.Sasl; 031import javax.security.sasl.SaslServer; 032import org.apache.hadoop.hbase.security.AccessDeniedException; 033import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; 034import org.apache.hadoop.hbase.security.SaslUtil; 035import org.apache.hadoop.security.UserGroupInformation; 036import org.apache.hadoop.security.token.SecretManager; 037import org.apache.hadoop.security.token.SecretManager.InvalidToken; 038import org.apache.hadoop.security.token.TokenIdentifier; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043@InterfaceAudience.Private 044public class DigestSaslServerAuthenticationProvider extends DigestSaslAuthenticationProvider 045 implements SaslServerAuthenticationProvider { 046 private static final Logger LOG = 047 LoggerFactory.getLogger(DigestSaslServerAuthenticationProvider.class); 048 049 private AtomicReference<UserGroupInformation> attemptingUser = new AtomicReference<>(null); 050 051 @Override 052 public AttemptingUserProvidingSaslServer 053 createServer(SecretManager<TokenIdentifier> secretManager, Map<String, String> saslProps) 054 throws IOException { 055 if (secretManager == null) { 056 throw new AccessDeniedException("Server is not configured to do DIGEST authentication."); 057 } 058 final SaslServer server = Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, 059 SaslUtil.SASL_DEFAULT_REALM, saslProps, 060 new SaslDigestCallbackHandler(secretManager, attemptingUser)); 061 062 return new AttemptingUserProvidingSaslServer(server, () -> attemptingUser.get()); 063 } 064 065 /** CallbackHandler for SASL DIGEST-MD5 mechanism */ 066 private static class SaslDigestCallbackHandler implements CallbackHandler { 067 private final SecretManager<TokenIdentifier> secretManager; 068 private final AtomicReference<UserGroupInformation> attemptingUser; 069 070 public SaslDigestCallbackHandler(SecretManager<TokenIdentifier> secretManager, 071 AtomicReference<UserGroupInformation> attemptingUser) { 072 this.secretManager = secretManager; 073 this.attemptingUser = attemptingUser; 074 } 075 076 private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken { 077 return SaslUtil.encodePassword(secretManager.retrievePassword(tokenid)); 078 } 079 080 /** {@inheritDoc} */ 081 @Override 082 public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbackException { 083 NameCallback nc = null; 084 PasswordCallback pc = null; 085 AuthorizeCallback ac = null; 086 for (Callback callback : callbacks) { 087 if (callback instanceof AuthorizeCallback) { 088 ac = (AuthorizeCallback) callback; 089 } else if (callback instanceof NameCallback) { 090 nc = (NameCallback) callback; 091 } else if (callback instanceof PasswordCallback) { 092 pc = (PasswordCallback) callback; 093 } else if (callback instanceof RealmCallback) { 094 continue; // realm is ignored 095 } else { 096 throw new UnsupportedCallbackException(callback, "Unrecognized SASL DIGEST-MD5 Callback"); 097 } 098 } 099 if (pc != null) { 100 TokenIdentifier tokenIdentifier = 101 HBaseSaslRpcServer.getIdentifier(nc.getDefaultName(), secretManager); 102 attemptingUser.set(tokenIdentifier.getUser()); 103 char[] password = getPassword(tokenIdentifier); 104 if (LOG.isTraceEnabled()) { 105 LOG.trace("SASL server DIGEST-MD5 callback: setting password for client: {}", 106 tokenIdentifier.getUser()); 107 } 108 pc.setPassword(password); 109 } 110 if (ac != null) { 111 // The authentication ID is the identifier (username) of the user who authenticated via 112 // SASL (the one who provided credentials). The authorization ID is who the remote user 113 // "asked" to be once they authenticated. This is akin to the UGI/JAAS "doAs" notion, e.g. 114 // authentication ID is the "real" user and authorization ID is the "proxy" user. 115 // 116 // For DelegationTokens: we do not expect any remote user with a delegation token to execute 117 // any RPCs as a user other than themselves. We disallow all cases where the real user 118 // does not match who the remote user wants to execute a request as someone else. 119 String authenticatedUserId = ac.getAuthenticationID(); 120 String userRequestedToExecuteAs = ac.getAuthorizationID(); 121 if (authenticatedUserId.equals(userRequestedToExecuteAs)) { 122 ac.setAuthorized(true); 123 if (LOG.isTraceEnabled()) { 124 String username = HBaseSaslRpcServer 125 .getIdentifier(userRequestedToExecuteAs, secretManager).getUser().getUserName(); 126 LOG.trace( 127 "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username); 128 } 129 ac.setAuthorizedID(userRequestedToExecuteAs); 130 } else { 131 ac.setAuthorized(false); 132 } 133 } 134 } 135 } 136 137 @Override 138 public boolean supportsProtocolAuthentication() { 139 return false; 140 } 141 142 @Override 143 public UserGroupInformation getAuthorizedUgi(String authzId, 144 SecretManager<TokenIdentifier> secretManager) throws IOException { 145 UserGroupInformation authorizedUgi; 146 TokenIdentifier tokenId = HBaseSaslRpcServer.getIdentifier(authzId, secretManager); 147 authorizedUgi = tokenId.getUser(); 148 if (authorizedUgi == null) { 149 throw new AccessDeniedException("Can't retrieve username from tokenIdentifier."); 150 } 151 authorizedUgi.addTokenIdentifier(tokenId); 152 authorizedUgi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); 153 return authorizedUgi; 154 } 155}