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}