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