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