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