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.BufferedInputStream;
22 import java.io.BufferedOutputStream;
23 import java.io.DataInputStream;
24 import java.io.DataOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
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.RealmCallback;
35 import javax.security.sasl.RealmChoiceCallback;
36 import javax.security.sasl.Sasl;
37 import javax.security.sasl.SaslException;
38 import javax.security.sasl.SaslClient;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.apache.hadoop.io.WritableUtils;
43 import org.apache.hadoop.ipc.RemoteException;
44 import org.apache.hadoop.hbase.security.HBaseSaslRpcServer.AuthMethod;
45 import org.apache.hadoop.hbase.security.HBaseSaslRpcServer.SaslStatus;
46 import org.apache.hadoop.security.SaslInputStream;
47 import org.apache.hadoop.security.SaslOutputStream;
48 import org.apache.hadoop.security.token.Token;
49 import org.apache.hadoop.security.token.TokenIdentifier;
50
51
52
53
54
55 public class HBaseSaslRpcClient {
56 public static final Log LOG = LogFactory.getLog(HBaseSaslRpcClient.class);
57
58 private final SaslClient saslClient;
59 private final boolean fallbackAllowed;
60
61
62
63
64
65
66
67
68
69 public HBaseSaslRpcClient(AuthMethod method,
70 Token<? extends TokenIdentifier> token, String serverPrincipal,
71 boolean fallbackAllowed) throws IOException {
72 this.fallbackAllowed = fallbackAllowed;
73 switch (method) {
74 case DIGEST:
75 if (LOG.isDebugEnabled())
76 LOG.debug("Creating SASL " + AuthMethod.DIGEST.getMechanismName()
77 + " client to authenticate to service at " + token.getService());
78 saslClient = Sasl.createSaslClient(new String[] { AuthMethod.DIGEST
79 .getMechanismName() }, null, null, HBaseSaslRpcServer.SASL_DEFAULT_REALM,
80 HBaseSaslRpcServer.SASL_PROPS, new SaslClientCallbackHandler(token));
81 break;
82 case KERBEROS:
83 case KERBEROS_USER_REALM:
84 if (LOG.isDebugEnabled()) {
85 LOG
86 .debug("Creating SASL " + AuthMethod.KERBEROS.getMechanismName()
87 + " client. Server's Kerberos principal name is "
88 + serverPrincipal);
89 }
90 if (serverPrincipal == null || serverPrincipal.length() == 0) {
91 throw new IOException(
92 "Failed to specify server's Kerberos principal name");
93 }
94 String names[] = HBaseSaslRpcServer.splitKerberosName(serverPrincipal);
95 if (names.length != 3) {
96 throw new IOException(
97 "Kerberos principal name does NOT have the expected hostname part: "
98 + serverPrincipal);
99 }
100 saslClient = Sasl.createSaslClient(new String[] { AuthMethod.KERBEROS
101 .getMechanismName() }, null, names[0], names[1],
102 HBaseSaslRpcServer.SASL_PROPS, null);
103 break;
104 default:
105 throw new IOException("Unknown authentication method " + method);
106 }
107 if (saslClient == null)
108 throw new IOException("Unable to find SASL client implementation");
109 }
110
111 private static void readStatus(DataInputStream inStream) throws IOException {
112 int id = inStream.readInt();
113 int status = inStream.readInt();
114 if (status != SaslStatus.SUCCESS.state) {
115 throw new RemoteException(WritableUtils.readString(inStream),
116 WritableUtils.readString(inStream));
117 }
118 }
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public boolean saslConnect(InputStream inS, OutputStream outS)
133 throws IOException {
134 DataInputStream inStream = new DataInputStream(new BufferedInputStream(inS));
135 DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(
136 outS));
137
138 try {
139 byte[] saslToken = new byte[0];
140 if (saslClient.hasInitialResponse())
141 saslToken = saslClient.evaluateChallenge(saslToken);
142 if (saslToken != null) {
143 outStream.writeInt(saslToken.length);
144 outStream.write(saslToken, 0, saslToken.length);
145 outStream.flush();
146 if (LOG.isDebugEnabled())
147 LOG.debug("Have sent token of size " + saslToken.length
148 + " from initSASLContext.");
149 }
150 if (!saslClient.isComplete()) {
151 readStatus(inStream);
152 int len = inStream.readInt();
153 if (len == HBaseSaslRpcServer.SWITCH_TO_SIMPLE_AUTH) {
154 if (!fallbackAllowed) {
155 throw new IOException("Server asks us to fall back to SIMPLE auth,"
156 + " but this client is configured to only allow secure"
157 + " connections.");
158 }
159 if (LOG.isDebugEnabled()) {
160 LOG.debug("Server asks us to fall back to simple auth.");
161 }
162 saslClient.dispose();
163 return false;
164 }
165 saslToken = new byte[len];
166 if (LOG.isDebugEnabled())
167 LOG.debug("Will read input token of size " + saslToken.length
168 + " for processing by initSASLContext");
169 inStream.readFully(saslToken);
170 }
171
172 while (!saslClient.isComplete()) {
173 saslToken = saslClient.evaluateChallenge(saslToken);
174 if (saslToken != null) {
175 if (LOG.isDebugEnabled())
176 LOG.debug("Will send token of size " + saslToken.length
177 + " from initSASLContext.");
178 outStream.writeInt(saslToken.length);
179 outStream.write(saslToken, 0, saslToken.length);
180 outStream.flush();
181 }
182 if (!saslClient.isComplete()) {
183 readStatus(inStream);
184 saslToken = new byte[inStream.readInt()];
185 if (LOG.isDebugEnabled())
186 LOG.debug("Will read input token of size " + saslToken.length
187 + " for processing by initSASLContext");
188 inStream.readFully(saslToken);
189 }
190 }
191 if (LOG.isDebugEnabled()) {
192 LOG.debug("SASL client context established. Negotiated QoP: "
193 + saslClient.getNegotiatedProperty(Sasl.QOP));
194 }
195 return true;
196 } catch (IOException e) {
197 try {
198 saslClient.dispose();
199 } catch (SaslException ignored) {
200
201 }
202 throw e;
203 }
204 }
205
206
207
208
209
210
211
212
213
214
215 public InputStream getInputStream(InputStream in) throws IOException {
216 if (!saslClient.isComplete()) {
217 throw new IOException("Sasl authentication exchange hasn't completed yet");
218 }
219 return new SaslInputStream(in, saslClient);
220 }
221
222
223
224
225
226
227
228
229
230
231 public OutputStream getOutputStream(OutputStream out) throws IOException {
232 if (!saslClient.isComplete()) {
233 throw new IOException("Sasl authentication exchange hasn't completed yet");
234 }
235 return new SaslOutputStream(out, saslClient);
236 }
237
238
239 public void dispose() throws SaslException {
240 saslClient.dispose();
241 }
242
243 private static class SaslClientCallbackHandler implements CallbackHandler {
244 private final String userName;
245 private final char[] userPassword;
246
247 public SaslClientCallbackHandler(Token<? extends TokenIdentifier> token) {
248 this.userName = HBaseSaslRpcServer.encodeIdentifier(token.getIdentifier());
249 this.userPassword = HBaseSaslRpcServer.encodePassword(token.getPassword());
250 }
251
252 public void handle(Callback[] callbacks)
253 throws UnsupportedCallbackException {
254 NameCallback nc = null;
255 PasswordCallback pc = null;
256 RealmCallback rc = null;
257 for (Callback callback : callbacks) {
258 if (callback instanceof RealmChoiceCallback) {
259 continue;
260 } else if (callback instanceof NameCallback) {
261 nc = (NameCallback) callback;
262 } else if (callback instanceof PasswordCallback) {
263 pc = (PasswordCallback) callback;
264 } else if (callback instanceof RealmCallback) {
265 rc = (RealmCallback) callback;
266 } else {
267 throw new UnsupportedCallbackException(callback,
268 "Unrecognized SASL client callback");
269 }
270 }
271 if (nc != null) {
272 if (LOG.isDebugEnabled())
273 LOG.debug("SASL client callback: setting username: " + userName);
274 nc.setName(userName);
275 }
276 if (pc != null) {
277 if (LOG.isDebugEnabled())
278 LOG.debug("SASL client callback: setting userPassword");
279 pc.setPassword(userPassword);
280 }
281 if (rc != null) {
282 if (LOG.isDebugEnabled())
283 LOG.debug("SASL client callback: setting realm: "
284 + rc.getDefaultText());
285 rc.setText(rc.getDefaultText());
286 }
287 }
288 }
289 }