2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package org
.apache
.hadoop
.hbase
.security
;
20 import java
.io
.ByteArrayInputStream
;
21 import java
.io
.DataInputStream
;
22 import java
.io
.IOException
;
23 import java
.security
.PrivilegedExceptionAction
;
26 import javax
.security
.auth
.callback
.Callback
;
27 import javax
.security
.auth
.callback
.CallbackHandler
;
28 import javax
.security
.auth
.callback
.NameCallback
;
29 import javax
.security
.auth
.callback
.PasswordCallback
;
30 import javax
.security
.auth
.callback
.UnsupportedCallbackException
;
31 import javax
.security
.sasl
.AuthorizeCallback
;
32 import javax
.security
.sasl
.RealmCallback
;
33 import javax
.security
.sasl
.Sasl
;
34 import javax
.security
.sasl
.SaslException
;
35 import javax
.security
.sasl
.SaslServer
;
37 import org
.apache
.yetus
.audience
.InterfaceAudience
;
38 import org
.slf4j
.Logger
;
39 import org
.slf4j
.LoggerFactory
;
40 import org
.apache
.hadoop
.security
.UserGroupInformation
;
41 import org
.apache
.hadoop
.security
.token
.SecretManager
;
42 import org
.apache
.hadoop
.security
.token
.SecretManager
.InvalidToken
;
43 import org
.apache
.hadoop
.security
.token
.TokenIdentifier
;
46 * A utility class that encapsulates SASL logic for RPC server. Copied from
47 * <code>org.apache.hadoop.security</code>
49 @InterfaceAudience.Private
50 public class HBaseSaslRpcServer
{
52 private static final Logger LOG
= LoggerFactory
.getLogger(HBaseSaslRpcServer
.class);
54 private final SaslServer saslServer
;
56 private UserGroupInformation attemptingUser
; // user name before auth
58 public HBaseSaslRpcServer(AuthMethod method
, Map
<String
, String
> saslProps
,
59 SecretManager
<TokenIdentifier
> secretManager
) throws IOException
{
62 if (secretManager
== null) {
63 throw new AccessDeniedException("Server is not configured to do DIGEST authentication.");
65 saslServer
= Sasl
.createSaslServer(AuthMethod
.DIGEST
.getMechanismName(), null,
66 SaslUtil
.SASL_DEFAULT_REALM
, saslProps
, new SaslDigestCallbackHandler(secretManager
));
69 UserGroupInformation current
= UserGroupInformation
.getCurrentUser();
70 String fullName
= current
.getUserName();
71 if (LOG
.isDebugEnabled()) {
72 LOG
.debug("Kerberos principal name is " + fullName
);
74 String
[] names
= SaslUtil
.splitKerberosName(fullName
);
75 if (names
.length
!= 3) {
76 throw new AccessDeniedException(
77 "Kerberos principal name does NOT have the expected " + "hostname part: " + fullName
);
80 saslServer
= current
.doAs(new PrivilegedExceptionAction
<SaslServer
>() {
82 public SaslServer
run() throws SaslException
{
83 return Sasl
.createSaslServer(AuthMethod
.KERBEROS
.getMechanismName(), names
[0],
84 names
[1], saslProps
, new SaslGssCallbackHandler());
87 } catch (InterruptedException e
) {
89 throw new AssertionError(e
);
93 throw new IOException("Unknown authentication method " + method
);
97 public boolean isComplete() {
98 return saslServer
.isComplete();
101 public byte[] evaluateResponse(byte[] response
) throws SaslException
{
102 return saslServer
.evaluateResponse(response
);
105 /** Release resources used by wrapped saslServer */
106 public void dispose() {
107 SaslUtil
.safeDispose(saslServer
);
110 public UserGroupInformation
getAttemptingUser() {
111 return attemptingUser
;
114 public byte[] wrap(byte[] buf
, int off
, int len
) throws SaslException
{
115 return saslServer
.wrap(buf
, off
, len
);
118 public byte[] unwrap(byte[] buf
, int off
, int len
) throws SaslException
{
119 return saslServer
.unwrap(buf
, off
, len
);
122 public String
getNegotiatedQop() {
123 return (String
) saslServer
.getNegotiatedProperty(Sasl
.QOP
);
126 public String
getAuthorizationID() {
127 return saslServer
.getAuthorizationID();
130 public static <T
extends TokenIdentifier
> T
getIdentifier(String id
,
131 SecretManager
<T
> secretManager
) throws InvalidToken
{
132 byte[] tokenId
= SaslUtil
.decodeIdentifier(id
);
133 T tokenIdentifier
= secretManager
.createIdentifier();
135 tokenIdentifier
.readFields(new DataInputStream(new ByteArrayInputStream(tokenId
)));
136 } catch (IOException e
) {
137 throw (InvalidToken
) new InvalidToken("Can't de-serialize tokenIdentifier").initCause(e
);
139 return tokenIdentifier
;
142 /** CallbackHandler for SASL DIGEST-MD5 mechanism */
143 private class SaslDigestCallbackHandler
implements CallbackHandler
{
144 private SecretManager
<TokenIdentifier
> secretManager
;
146 public SaslDigestCallbackHandler(SecretManager
<TokenIdentifier
> secretManager
) {
147 this.secretManager
= secretManager
;
150 private char[] getPassword(TokenIdentifier tokenid
) throws InvalidToken
{
151 return SaslUtil
.encodePassword(secretManager
.retrievePassword(tokenid
));
156 public void handle(Callback
[] callbacks
) throws InvalidToken
, UnsupportedCallbackException
{
157 NameCallback nc
= null;
158 PasswordCallback pc
= null;
159 AuthorizeCallback ac
= null;
160 for (Callback callback
: callbacks
) {
161 if (callback
instanceof AuthorizeCallback
) {
162 ac
= (AuthorizeCallback
) callback
;
163 } else if (callback
instanceof NameCallback
) {
164 nc
= (NameCallback
) callback
;
165 } else if (callback
instanceof PasswordCallback
) {
166 pc
= (PasswordCallback
) callback
;
167 } else if (callback
instanceof RealmCallback
) {
168 continue; // realm is ignored
170 throw new UnsupportedCallbackException(callback
, "Unrecognized SASL DIGEST-MD5 Callback");
174 TokenIdentifier tokenIdentifier
= getIdentifier(nc
.getDefaultName(), secretManager
);
175 char[] password
= getPassword(tokenIdentifier
);
176 UserGroupInformation user
= tokenIdentifier
.getUser(); // may throw exception
177 attemptingUser
= user
;
178 if (LOG
.isTraceEnabled()) {
179 LOG
.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " +
180 tokenIdentifier
.getUser());
182 pc
.setPassword(password
);
185 String authid
= ac
.getAuthenticationID();
186 String authzid
= ac
.getAuthorizationID();
187 if (authid
.equals(authzid
)) {
188 ac
.setAuthorized(true);
190 ac
.setAuthorized(false);
192 if (ac
.isAuthorized()) {
193 if (LOG
.isTraceEnabled()) {
194 String username
= getIdentifier(authzid
, secretManager
).getUser().getUserName();
196 "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username
);
198 ac
.setAuthorizedID(authzid
);
204 /** CallbackHandler for SASL GSSAPI Kerberos mechanism */
205 private static class SaslGssCallbackHandler
implements CallbackHandler
{
209 public void handle(Callback
[] callbacks
) throws UnsupportedCallbackException
{
210 AuthorizeCallback ac
= null;
211 for (Callback callback
: callbacks
) {
212 if (callback
instanceof AuthorizeCallback
) {
213 ac
= (AuthorizeCallback
) callback
;
215 throw new UnsupportedCallbackException(callback
, "Unrecognized SASL GSSAPI Callback");
219 String authid
= ac
.getAuthenticationID();
220 String authzid
= ac
.getAuthorizationID();
221 if (authid
.equals(authzid
)) {
222 ac
.setAuthorized(true);
224 ac
.setAuthorized(false);
226 if (ac
.isAuthorized()) {
227 if (LOG
.isDebugEnabled()) {
229 "SASL server GSSAPI callback: setting " + "canonicalized client ID: " + authzid
);
231 ac
.setAuthorizedID(authzid
);