1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
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
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
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
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
164 public String getMechanismName() {
165 return mechanismName;
166 }
167
168
169 public static AuthMethod read(DataInput in) throws IOException {
170 return valueOf(in.readByte());
171 }
172
173
174 public void write(DataOutput out) throws IOException {
175 out.write(code);
176 }
177 };
178
179
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
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;
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();
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
250 public static class SaslGssCallbackHandler implements CallbackHandler {
251
252
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 }