View Javadoc

1   /**
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
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
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.
17   */
18  package org.apache.hadoop.hbase.security;
19  
20  import com.google.common.annotations.VisibleForTesting;
21  
22  import java.io.IOException;
23  import java.util.Map;
24  
25  import javax.security.auth.callback.Callback;
26  import javax.security.auth.callback.CallbackHandler;
27  import javax.security.auth.callback.NameCallback;
28  import javax.security.auth.callback.PasswordCallback;
29  import javax.security.auth.callback.UnsupportedCallbackException;
30  import javax.security.sasl.RealmCallback;
31  import javax.security.sasl.RealmChoiceCallback;
32  import javax.security.sasl.Sasl;
33  import javax.security.sasl.SaslClient;
34  import javax.security.sasl.SaslException;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.hbase.classification.InterfaceAudience;
39  import org.apache.hadoop.security.token.Token;
40  import org.apache.hadoop.security.token.TokenIdentifier;
41  
42  /**
43   * A utility class that encapsulates SASL logic for RPC client. Copied from
44   * <code>org.apache.hadoop.security</code>
45   */
46  @InterfaceAudience.Private
47  public abstract class AbstractHBaseSaslRpcClient {
48  
49    private static final Log LOG = LogFactory.getLog(AbstractHBaseSaslRpcClient.class);
50  
51    private static final byte[] EMPTY_TOKEN = new byte[0];
52  
53    protected final SaslClient saslClient;
54  
55    protected final boolean fallbackAllowed;
56  
57    protected final Map<String, String> saslProps;
58  
59    /**
60     * Create a HBaseSaslRpcClient for an authentication method
61     * @param method the requested authentication method
62     * @param token token to use if needed by the authentication method
63     * @param serverPrincipal the server principal that we are trying to set the connection up to
64     * @param fallbackAllowed does the client allow fallback to simple authentication
65     * @throws IOException
66     */
67    protected AbstractHBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token,
68        String serverPrincipal, boolean fallbackAllowed) throws IOException {
69      this(method, token, serverPrincipal, fallbackAllowed, "authentication");
70    }
71  
72    /**
73     * Create a HBaseSaslRpcClient for an authentication method
74     * @param method the requested authentication method
75     * @param token token to use if needed by the authentication method
76     * @param serverPrincipal the server principal that we are trying to set the connection up to
77     * @param fallbackAllowed does the client allow fallback to simple authentication
78     * @param rpcProtection the protection level ("authentication", "integrity" or "privacy")
79     * @throws IOException
80     */
81    protected AbstractHBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token,
82        String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException {
83      this.fallbackAllowed = fallbackAllowed;
84      saslProps = SaslUtil.initSaslProperties(rpcProtection);
85      switch (method) {
86        case DIGEST:
87          if (LOG.isDebugEnabled()) LOG.debug("Creating SASL " + AuthMethod.DIGEST.getMechanismName()
88              + " client to authenticate to service at " + token.getService());
89          saslClient = createDigestSaslClient(new String[] { AuthMethod.DIGEST.getMechanismName() },
90            SaslUtil.SASL_DEFAULT_REALM, new SaslClientCallbackHandler(token));
91          break;
92        case KERBEROS:
93          if (LOG.isDebugEnabled()) {
94            LOG.debug("Creating SASL " + AuthMethod.KERBEROS.getMechanismName()
95                + " client. Server's Kerberos principal name is " + serverPrincipal);
96          }
97          if (serverPrincipal == null || serverPrincipal.length() == 0) {
98            throw new IOException("Failed to specify server's Kerberos principal name");
99          }
100         String[] names = SaslUtil.splitKerberosName(serverPrincipal);
101         if (names.length != 3) {
102           throw new IOException(
103               "Kerberos principal does not have the expected format: " + serverPrincipal);
104         }
105         saslClient = createKerberosSaslClient(
106           new String[] { AuthMethod.KERBEROS.getMechanismName() }, names[0], names[1]);
107         break;
108       default:
109         throw new IOException("Unknown authentication method " + method);
110     }
111     if (saslClient == null) {
112       throw new IOException("Unable to find SASL client implementation");
113     }
114   }
115 
116   protected SaslClient createDigestSaslClient(String[] mechanismNames, String saslDefaultRealm,
117       CallbackHandler saslClientCallbackHandler) throws IOException {
118     return Sasl.createSaslClient(mechanismNames, null, null, saslDefaultRealm, saslProps,
119       saslClientCallbackHandler);
120   }
121 
122   protected SaslClient createKerberosSaslClient(String[] mechanismNames, String userFirstPart,
123       String userSecondPart) throws IOException {
124     return Sasl.createSaslClient(mechanismNames, null, userFirstPart, userSecondPart, saslProps,
125       null);
126   }
127 
128   public byte[] getInitialResponse() throws SaslException {
129     if (saslClient.hasInitialResponse()) {
130       return saslClient.evaluateChallenge(EMPTY_TOKEN);
131     } else {
132       return EMPTY_TOKEN;
133     }
134   }
135 
136   public boolean isComplete() {
137     return saslClient.isComplete();
138   }
139 
140   public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
141     return saslClient.evaluateChallenge(challenge);
142   }
143 
144   /** Release resources used by wrapped saslClient */
145   public void dispose() {
146     SaslUtil.safeDispose(saslClient);
147   }
148 
149   @VisibleForTesting
150   static class SaslClientCallbackHandler implements CallbackHandler {
151     private final String userName;
152     private final char[] userPassword;
153 
154     public SaslClientCallbackHandler(Token<? extends TokenIdentifier> token) {
155       this.userName = SaslUtil.encodeIdentifier(token.getIdentifier());
156       this.userPassword = SaslUtil.encodePassword(token.getPassword());
157     }
158 
159     @Override
160     public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
161       NameCallback nc = null;
162       PasswordCallback pc = null;
163       RealmCallback rc = null;
164       for (Callback callback : callbacks) {
165         if (callback instanceof RealmChoiceCallback) {
166           continue;
167         } else if (callback instanceof NameCallback) {
168           nc = (NameCallback) callback;
169         } else if (callback instanceof PasswordCallback) {
170           pc = (PasswordCallback) callback;
171         } else if (callback instanceof RealmCallback) {
172           rc = (RealmCallback) callback;
173         } else {
174           throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback");
175         }
176       }
177       if (nc != null) {
178         if (LOG.isDebugEnabled()) {
179           LOG.debug("SASL client callback: setting username: " + userName);
180         }
181         nc.setName(userName);
182       }
183       if (pc != null) {
184         if (LOG.isDebugEnabled()) {
185           LOG.debug("SASL client callback: setting userPassword");
186         }
187         pc.setPassword(userPassword);
188       }
189       if (rc != null) {
190         if (LOG.isDebugEnabled()) {
191           LOG.debug("SASL client callback: setting realm: " + rc.getDefaultText());
192         }
193         rc.setText(rc.getDefaultText());
194       }
195     }
196   }
197 }