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  
19  package org.apache.hadoop.hbase.security;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.DataInput;
23  import java.io.DataInputStream;
24  import java.io.DataOutput;
25  import java.io.IOException;
26  import java.util.Map;
27  import java.util.TreeMap;
28  
29  import javax.security.auth.callback.Callback;
30  import javax.security.auth.callback.CallbackHandler;
31  import javax.security.auth.callback.NameCallback;
32  import javax.security.auth.callback.PasswordCallback;
33  import javax.security.auth.callback.UnsupportedCallbackException;
34  import javax.security.sasl.AuthorizeCallback;
35  import javax.security.sasl.RealmCallback;
36  import javax.security.sasl.Sasl;
37  
38  import org.apache.commons.codec.binary.Base64;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.hadoop.conf.Configuration;
42  import org.apache.hadoop.hbase.ipc.HBaseServer;
43  import org.apache.hadoop.hbase.ipc.SecureServer;
44  import org.apache.hadoop.ipc.Server;
45  import org.apache.hadoop.security.UserGroupInformation;
46  import org.apache.hadoop.security.token.SecretManager;
47  import org.apache.hadoop.security.token.TokenIdentifier;
48  import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
49  import org.apache.hadoop.security.token.SecretManager.InvalidToken;
50  
51  /**
52   * A utility class for dealing with SASL on RPC server
53   */
54  public class HBaseSaslRpcServer {
55    public static final Log LOG = LogFactory.getLog(HBaseSaslRpcServer.class);
56    public static final String SASL_DEFAULT_REALM = "default";
57    public static final Map<String, String> SASL_PROPS =
58        new TreeMap<String, String>();
59  
60    public static final int SWITCH_TO_SIMPLE_AUTH = -88;
61  
62    public static enum QualityOfProtection {
63      AUTHENTICATION("auth"),
64      INTEGRITY("auth-int"),
65      PRIVACY("auth-conf");
66  
67      public final String saslQop;
68  
69      private QualityOfProtection(String saslQop) {
70        this.saslQop = saslQop;
71      }
72  
73      public String getSaslQop() {
74        return saslQop;
75      }
76    }
77  
78    public static void init(Configuration conf) {
79      QualityOfProtection saslQOP = QualityOfProtection.AUTHENTICATION;
80      String rpcProtection = conf.get("hbase.rpc.protection",
81          QualityOfProtection.AUTHENTICATION.name().toLowerCase());
82      if (QualityOfProtection.INTEGRITY.name().toLowerCase()
83          .equals(rpcProtection)) {
84        saslQOP = QualityOfProtection.INTEGRITY;
85      } else if (QualityOfProtection.PRIVACY.name().toLowerCase().equals(
86          rpcProtection)) {
87        saslQOP = QualityOfProtection.PRIVACY;
88      }
89  
90      SASL_PROPS.put(Sasl.QOP, saslQOP.getSaslQop());
91      SASL_PROPS.put(Sasl.SERVER_AUTH, "true");
92    }
93  
94    static String encodeIdentifier(byte[] identifier) {
95      return new String(Base64.encodeBase64(identifier));
96    }
97  
98    static byte[] decodeIdentifier(String identifier) {
99      return Base64.decodeBase64(identifier.getBytes());
100   }
101 
102   public static <T extends TokenIdentifier> T getIdentifier(String id,
103       SecretManager<T> secretManager) throws InvalidToken {
104     byte[] tokenId = decodeIdentifier(id);
105     T tokenIdentifier = secretManager.createIdentifier();
106     try {
107       tokenIdentifier.readFields(new DataInputStream(new ByteArrayInputStream(
108           tokenId)));
109     } catch (IOException e) {
110       throw (InvalidToken) new InvalidToken(
111           "Can't de-serialize tokenIdentifier").initCause(e);
112     }
113     return tokenIdentifier;
114   }
115 
116   static char[] encodePassword(byte[] password) {
117     return new String(Base64.encodeBase64(password)).toCharArray();
118   }
119 
120   /** Splitting fully qualified Kerberos name into parts */
121   public static String[] splitKerberosName(String fullName) {
122     return fullName.split("[/@]");
123   }
124 
125   public enum SaslStatus {
126     SUCCESS (0),
127     ERROR (1);
128     
129     public final int state;
130     private SaslStatus(int state) {
131       this.state = state;
132     }
133   }
134   
135   /** Authentication method */
136   public static enum AuthMethod {
137     SIMPLE((byte) 80, "", AuthenticationMethod.SIMPLE),
138     KERBEROS((byte) 81, "GSSAPI", AuthenticationMethod.KERBEROS),
139     DIGEST((byte) 82, "DIGEST-MD5", AuthenticationMethod.TOKEN),
140     KERBEROS_USER_REALM((byte) 83, "GSSAPI", AuthenticationMethod.KERBEROS);
141 
142 
143     /** The code for this method. */
144     public final byte code;
145     public final String mechanismName;
146     public final AuthenticationMethod authenticationMethod;
147 
148     private AuthMethod(byte code, String mechanismName, 
149                        AuthenticationMethod authMethod) {
150       this.code = code;
151       this.mechanismName = mechanismName;
152       this.authenticationMethod = authMethod;
153     }
154 
155     private static final int FIRST_CODE = values()[0].code;
156 
157     /** Return the object represented by the code. */
158     private static AuthMethod valueOf(byte code) {
159       final int i = (code & 0xff) - FIRST_CODE;
160       return i < 0 || i >= values().length ? null : values()[i];
161     }
162 
163     /** Return the SASL mechanism name */
164     public String getMechanismName() {
165       return mechanismName;
166     }
167 
168     /** Read from in */
169     public static AuthMethod read(DataInput in) throws IOException {
170       return valueOf(in.readByte());
171     }
172 
173     /** Write to out */
174     public void write(DataOutput out) throws IOException {
175       out.write(code);
176     }
177   };
178 
179   /** CallbackHandler for SASL DIGEST-MD5 mechanism */
180   public static class SaslDigestCallbackHandler implements CallbackHandler {
181     private SecretManager<TokenIdentifier> secretManager;
182     private SecureServer.SecureConnection connection;
183 
184     public SaslDigestCallbackHandler(
185         SecretManager<TokenIdentifier> secretManager,
186         SecureServer.SecureConnection connection) {
187       this.secretManager = secretManager;
188       this.connection = connection;
189     }
190 
191     private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken {
192       return encodePassword(secretManager.retrievePassword(tokenid));
193     }
194 
195     /** {@inheritDoc} */
196     @Override
197     public void handle(Callback[] callbacks) throws InvalidToken,
198         UnsupportedCallbackException {
199       NameCallback nc = null;
200       PasswordCallback pc = null;
201       AuthorizeCallback ac = null;
202       for (Callback callback : callbacks) {
203         if (callback instanceof AuthorizeCallback) {
204           ac = (AuthorizeCallback) callback;
205         } else if (callback instanceof NameCallback) {
206           nc = (NameCallback) callback;
207         } else if (callback instanceof PasswordCallback) {
208           pc = (PasswordCallback) callback;
209         } else if (callback instanceof RealmCallback) {
210           continue; // realm is ignored
211         } else {
212           throw new UnsupportedCallbackException(callback,
213               "Unrecognized SASL DIGEST-MD5 Callback");
214         }
215       }
216       if (pc != null) {
217         TokenIdentifier tokenIdentifier = getIdentifier(nc.getDefaultName(), secretManager);
218         char[] password = getPassword(tokenIdentifier);
219         UserGroupInformation user = null;
220         user = tokenIdentifier.getUser(); // may throw exception
221         connection.attemptingUser = user;
222         if (LOG.isDebugEnabled()) {
223           LOG.debug("SASL server DIGEST-MD5 callback: setting password "
224               + "for client: " + tokenIdentifier.getUser());
225         }
226         pc.setPassword(password);
227       }
228       if (ac != null) {
229         String authid = ac.getAuthenticationID();
230         String authzid = ac.getAuthorizationID();
231         if (authid.equals(authzid)) {
232           ac.setAuthorized(true);
233         } else {
234           ac.setAuthorized(false);
235         }
236         if (ac.isAuthorized()) {
237           if (LOG.isDebugEnabled()) {
238             String username =
239               getIdentifier(authzid, secretManager).getUser().getUserName();
240             LOG.debug("SASL server DIGEST-MD5 callback: setting "
241                 + "canonicalized client ID: " + username);
242           }
243           ac.setAuthorizedID(authzid);
244         }
245       }
246     }
247   }
248 
249   /** CallbackHandler for SASL GSSAPI Kerberos mechanism */
250   public static class SaslGssCallbackHandler implements CallbackHandler {
251 
252     /** {@inheritDoc} */
253     @Override
254     public void handle(Callback[] callbacks) throws
255         UnsupportedCallbackException {
256       AuthorizeCallback ac = null;
257       for (Callback callback : callbacks) {
258         if (callback instanceof AuthorizeCallback) {
259           ac = (AuthorizeCallback) callback;
260         } else {
261           throw new UnsupportedCallbackException(callback,
262               "Unrecognized SASL GSSAPI Callback");
263         }
264       }
265       if (ac != null) {
266         String authid = ac.getAuthenticationID();
267         String authzid = ac.getAuthorizationID();
268         if (authid.equals(authzid)) {
269           ac.setAuthorized(true);
270         } else {
271           ac.setAuthorized(false);
272         }
273         if (ac.isAuthorized()) {
274           if (LOG.isDebugEnabled())
275             LOG.debug("SASL server GSSAPI callback: setting "
276                 + "canonicalized client ID: " + authzid);
277           ac.setAuthorizedID(authzid);
278         }
279       }
280     }
281   }
282 }