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.DataInputStream;
23  import java.io.IOException;
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.AuthorizeCallback;
31  import javax.security.sasl.RealmCallback;
32  import javax.security.sasl.Sasl;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.ipc.RpcServer;
38  import org.apache.hadoop.security.UserGroupInformation;
39  import org.apache.hadoop.security.token.SecretManager;
40  import org.apache.hadoop.security.token.TokenIdentifier;
41  import org.apache.hadoop.security.token.SecretManager.InvalidToken;
42  
43  /**
44   * A utility class for dealing with SASL on RPC server
45   */
46  public class HBaseSaslRpcServer {
47    public static final Log LOG = LogFactory.getLog(HBaseSaslRpcServer.class);
48  
49    public static enum QualityOfProtection {
50      AUTHENTICATION("auth"),
51      INTEGRITY("auth-int"),
52      PRIVACY("auth-conf");
53  
54      public final String saslQop;
55  
56      private QualityOfProtection(String saslQop) {
57        this.saslQop = saslQop;
58      }
59  
60      public String getSaslQop() {
61        return saslQop;
62      }
63    }
64  
65    public static void init(Configuration conf) {
66      QualityOfProtection saslQOP = QualityOfProtection.AUTHENTICATION;
67      String rpcProtection = conf.get("hbase.rpc.protection",
68          QualityOfProtection.AUTHENTICATION.name().toLowerCase());
69      if (QualityOfProtection.INTEGRITY.name().toLowerCase()
70          .equals(rpcProtection)) {
71        saslQOP = QualityOfProtection.INTEGRITY;
72      } else if (QualityOfProtection.PRIVACY.name().toLowerCase().equals(
73          rpcProtection)) {
74        saslQOP = QualityOfProtection.PRIVACY;
75      }
76  
77      SaslUtil.SASL_PROPS.put(Sasl.QOP, saslQOP.getSaslQop());
78      SaslUtil.SASL_PROPS.put(Sasl.SERVER_AUTH, "true");
79    }
80  
81    public static <T extends TokenIdentifier> T getIdentifier(String id,
82        SecretManager<T> secretManager) throws InvalidToken {
83      byte[] tokenId = SaslUtil.decodeIdentifier(id);
84      T tokenIdentifier = secretManager.createIdentifier();
85      try {
86        tokenIdentifier.readFields(new DataInputStream(new ByteArrayInputStream(
87            tokenId)));
88      } catch (IOException e) {
89        throw (InvalidToken) new InvalidToken(
90            "Can't de-serialize tokenIdentifier").initCause(e);
91      }
92      return tokenIdentifier;
93    }
94  
95  
96    /** CallbackHandler for SASL DIGEST-MD5 mechanism */
97    public static class SaslDigestCallbackHandler implements CallbackHandler {
98      private SecretManager<TokenIdentifier> secretManager;
99      private RpcServer.Connection connection;
100 
101     public SaslDigestCallbackHandler(
102         SecretManager<TokenIdentifier> secretManager,
103         RpcServer.Connection connection) {
104       this.secretManager = secretManager;
105       this.connection = connection;
106     }
107 
108     private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken {
109       return SaslUtil.encodePassword(secretManager.retrievePassword(tokenid));
110     }
111 
112     /** {@inheritDoc} */
113     @Override
114     public void handle(Callback[] callbacks) throws InvalidToken,
115         UnsupportedCallbackException {
116       NameCallback nc = null;
117       PasswordCallback pc = null;
118       AuthorizeCallback ac = null;
119       for (Callback callback : callbacks) {
120         if (callback instanceof AuthorizeCallback) {
121           ac = (AuthorizeCallback) callback;
122         } else if (callback instanceof NameCallback) {
123           nc = (NameCallback) callback;
124         } else if (callback instanceof PasswordCallback) {
125           pc = (PasswordCallback) callback;
126         } else if (callback instanceof RealmCallback) {
127           continue; // realm is ignored
128         } else {
129           throw new UnsupportedCallbackException(callback,
130               "Unrecognized SASL DIGEST-MD5 Callback");
131         }
132       }
133       if (pc != null) {
134         TokenIdentifier tokenIdentifier = getIdentifier(nc.getDefaultName(), secretManager);
135         char[] password = getPassword(tokenIdentifier);
136         UserGroupInformation user = null;
137         user = tokenIdentifier.getUser(); // may throw exception
138         connection.attemptingUser = user;
139         if (LOG.isDebugEnabled()) {
140           LOG.debug("SASL server DIGEST-MD5 callback: setting password "
141               + "for client: " + tokenIdentifier.getUser());
142         }
143         pc.setPassword(password);
144       }
145       if (ac != null) {
146         String authid = ac.getAuthenticationID();
147         String authzid = ac.getAuthorizationID();
148         if (authid.equals(authzid)) {
149           ac.setAuthorized(true);
150         } else {
151           ac.setAuthorized(false);
152         }
153         if (ac.isAuthorized()) {
154           if (LOG.isDebugEnabled()) {
155             String username =
156               getIdentifier(authzid, secretManager).getUser().getUserName();
157             LOG.debug("SASL server DIGEST-MD5 callback: setting "
158                 + "canonicalized client ID: " + username);
159           }
160           ac.setAuthorizedID(authzid);
161         }
162       }
163     }
164   }
165 
166   /** CallbackHandler for SASL GSSAPI Kerberos mechanism */
167   public static class SaslGssCallbackHandler implements CallbackHandler {
168 
169     /** {@inheritDoc} */
170     @Override
171     public void handle(Callback[] callbacks) throws
172         UnsupportedCallbackException {
173       AuthorizeCallback ac = null;
174       for (Callback callback : callbacks) {
175         if (callback instanceof AuthorizeCallback) {
176           ac = (AuthorizeCallback) callback;
177         } else {
178           throw new UnsupportedCallbackException(callback,
179               "Unrecognized SASL GSSAPI Callback");
180         }
181       }
182       if (ac != null) {
183         String authid = ac.getAuthenticationID();
184         String authzid = ac.getAuthorizationID();
185         if (authid.equals(authzid)) {
186           ac.setAuthorized(true);
187         } else {
188           ac.setAuthorized(false);
189         }
190         if (ac.isAuthorized()) {
191           if (LOG.isDebugEnabled())
192             LOG.debug("SASL server GSSAPI callback: setting "
193                 + "canonicalized client ID: " + authzid);
194           ac.setAuthorizedID(authzid);
195         }
196       }
197     }
198   }
199 }