1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.security;
19
20 import com.google.common.annotations.VisibleForTesting;
21
22 import java.io.IOException;
23 import java.util.Map;
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.RealmCallback;
31 import javax.security.sasl.RealmChoiceCallback;
32 import javax.security.sasl.Sasl;
33 import javax.security.sasl.SaslClient;
34 import javax.security.sasl.SaslException;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.hadoop.hbase.classification.InterfaceAudience;
39 import org.apache.hadoop.security.token.Token;
40 import org.apache.hadoop.security.token.TokenIdentifier;
41
42
43
44
45
46 @InterfaceAudience.Private
47 public abstract class AbstractHBaseSaslRpcClient {
48
49 private static final Log LOG = LogFactory.getLog(AbstractHBaseSaslRpcClient.class);
50
51 private static final byte[] EMPTY_TOKEN = new byte[0];
52
53 protected final SaslClient saslClient;
54
55 protected final boolean fallbackAllowed;
56
57 protected final Map<String, String> saslProps;
58
59
60
61
62
63
64
65
66
67 protected AbstractHBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token,
68 String serverPrincipal, boolean fallbackAllowed) throws IOException {
69 this(method, token, serverPrincipal, fallbackAllowed, "authentication");
70 }
71
72
73
74
75
76
77
78
79
80
81 protected AbstractHBaseSaslRpcClient(AuthMethod method, Token<? extends TokenIdentifier> token,
82 String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException {
83 this.fallbackAllowed = fallbackAllowed;
84 saslProps = SaslUtil.initSaslProperties(rpcProtection);
85 switch (method) {
86 case DIGEST:
87 if (LOG.isDebugEnabled()) LOG.debug("Creating SASL " + AuthMethod.DIGEST.getMechanismName()
88 + " client to authenticate to service at " + token.getService());
89 saslClient = createDigestSaslClient(new String[] { AuthMethod.DIGEST.getMechanismName() },
90 SaslUtil.SASL_DEFAULT_REALM, new SaslClientCallbackHandler(token));
91 break;
92 case KERBEROS:
93 if (LOG.isDebugEnabled()) {
94 LOG.debug("Creating SASL " + AuthMethod.KERBEROS.getMechanismName()
95 + " client. Server's Kerberos principal name is " + serverPrincipal);
96 }
97 if (serverPrincipal == null || serverPrincipal.length() == 0) {
98 throw new IOException("Failed to specify server's Kerberos principal name");
99 }
100 String[] names = SaslUtil.splitKerberosName(serverPrincipal);
101 if (names.length != 3) {
102 throw new IOException(
103 "Kerberos principal does not have the expected format: " + serverPrincipal);
104 }
105 saslClient = createKerberosSaslClient(
106 new String[] { AuthMethod.KERBEROS.getMechanismName() }, names[0], names[1]);
107 break;
108 default:
109 throw new IOException("Unknown authentication method " + method);
110 }
111 if (saslClient == null) {
112 throw new IOException("Unable to find SASL client implementation");
113 }
114 }
115
116 protected SaslClient createDigestSaslClient(String[] mechanismNames, String saslDefaultRealm,
117 CallbackHandler saslClientCallbackHandler) throws IOException {
118 return Sasl.createSaslClient(mechanismNames, null, null, saslDefaultRealm, saslProps,
119 saslClientCallbackHandler);
120 }
121
122 protected SaslClient createKerberosSaslClient(String[] mechanismNames, String userFirstPart,
123 String userSecondPart) throws IOException {
124 return Sasl.createSaslClient(mechanismNames, null, userFirstPart, userSecondPart, saslProps,
125 null);
126 }
127
128 public byte[] getInitialResponse() throws SaslException {
129 if (saslClient.hasInitialResponse()) {
130 return saslClient.evaluateChallenge(EMPTY_TOKEN);
131 } else {
132 return EMPTY_TOKEN;
133 }
134 }
135
136 public boolean isComplete() {
137 return saslClient.isComplete();
138 }
139
140 public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
141 return saslClient.evaluateChallenge(challenge);
142 }
143
144
145 public void dispose() {
146 SaslUtil.safeDispose(saslClient);
147 }
148
149 @VisibleForTesting
150 static class SaslClientCallbackHandler implements CallbackHandler {
151 private final String userName;
152 private final char[] userPassword;
153
154 public SaslClientCallbackHandler(Token<? extends TokenIdentifier> token) {
155 this.userName = SaslUtil.encodeIdentifier(token.getIdentifier());
156 this.userPassword = SaslUtil.encodePassword(token.getPassword());
157 }
158
159 @Override
160 public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
161 NameCallback nc = null;
162 PasswordCallback pc = null;
163 RealmCallback rc = null;
164 for (Callback callback : callbacks) {
165 if (callback instanceof RealmChoiceCallback) {
166 continue;
167 } else if (callback instanceof NameCallback) {
168 nc = (NameCallback) callback;
169 } else if (callback instanceof PasswordCallback) {
170 pc = (PasswordCallback) callback;
171 } else if (callback instanceof RealmCallback) {
172 rc = (RealmCallback) callback;
173 } else {
174 throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback");
175 }
176 }
177 if (nc != null) {
178 if (LOG.isDebugEnabled()) {
179 LOG.debug("SASL client callback: setting username: " + userName);
180 }
181 nc.setName(userName);
182 }
183 if (pc != null) {
184 if (LOG.isDebugEnabled()) {
185 LOG.debug("SASL client callback: setting userPassword");
186 }
187 pc.setPassword(userPassword);
188 }
189 if (rc != null) {
190 if (LOG.isDebugEnabled()) {
191 LOG.debug("SASL client callback: setting realm: " + rc.getDefaultText());
192 }
193 rc.setText(rc.getDefaultText());
194 }
195 }
196 }
197 }