1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.thrift;
20
21 import java.io.IOException;
22 import java.security.PrivilegedExceptionAction;
23
24 import javax.servlet.ServletException;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.commons.net.util.Base64;
31 import org.apache.hadoop.conf.Configuration;
32 import org.apache.hadoop.hbase.classification.InterfaceAudience;
33 import org.apache.hadoop.hbase.security.SecurityUtil;
34 import org.apache.hadoop.security.UserGroupInformation;
35 import org.apache.hadoop.security.authorize.AuthorizationException;
36 import org.apache.hadoop.security.authorize.ProxyUsers;
37 import org.apache.thrift.TProcessor;
38 import org.apache.thrift.protocol.TProtocolFactory;
39 import org.apache.thrift.server.TServlet;
40 import org.ietf.jgss.GSSContext;
41 import org.ietf.jgss.GSSCredential;
42 import org.ietf.jgss.GSSException;
43 import org.ietf.jgss.GSSManager;
44 import org.ietf.jgss.GSSName;
45 import org.ietf.jgss.Oid;
46
47
48
49
50
51 @InterfaceAudience.Private
52 public class ThriftHttpServlet extends TServlet {
53 private static final long serialVersionUID = 1L;
54 public static final Log LOG = LogFactory.getLog(ThriftHttpServlet.class.getName());
55 private transient final UserGroupInformation realUser;
56 private transient final Configuration conf;
57 private final boolean securityEnabled;
58 private final boolean doAsEnabled;
59 private transient ThriftServerRunner.HBaseHandler hbaseHandler;
60
61 public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory,
62 UserGroupInformation realUser, Configuration conf, ThriftServerRunner.HBaseHandler
63 hbaseHandler, boolean securityEnabled, boolean doAsEnabled) {
64 super(processor, protocolFactory);
65 this.realUser = realUser;
66 this.conf = conf;
67 this.hbaseHandler = hbaseHandler;
68 this.securityEnabled = securityEnabled;
69 this.doAsEnabled = doAsEnabled;
70 }
71
72 @Override
73 protected void doPost(HttpServletRequest request, HttpServletResponse response)
74 throws ServletException, IOException {
75 String effectiveUser = realUser.getShortUserName();
76 if (securityEnabled) {
77 try {
78
79
80 effectiveUser = doKerberosAuth(request);
81 } catch (HttpAuthenticationException e) {
82 LOG.error("Kerberos Authentication failed", e);
83
84 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
85 response.getWriter().println("Authentication Error: " + e.getMessage());
86 }
87 }
88 String doAsUserFromQuery = request.getHeader("doAs");
89 if (doAsUserFromQuery != null) {
90 if (!doAsEnabled) {
91 throw new ServletException("Support for proxyuser is not configured");
92 }
93
94
95 UserGroupInformation ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, realUser);
96
97 try {
98 ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf);
99 } catch (AuthorizationException e) {
100 throw new ServletException(e.getMessage());
101 }
102 effectiveUser = doAsUserFromQuery;
103 }
104 hbaseHandler.setEffectiveUser(effectiveUser);
105 super.doPost(request, response);
106 }
107
108
109
110
111
112
113 private String doKerberosAuth(HttpServletRequest request)
114 throws HttpAuthenticationException {
115 try {
116 return realUser.doAs(new HttpKerberosServerAction(request, realUser));
117 } catch (Exception e) {
118 LOG.error("Failed to perform authentication");
119 throw new HttpAuthenticationException(e);
120 }
121 }
122
123
124 private static class HttpKerberosServerAction implements PrivilegedExceptionAction<String> {
125 HttpServletRequest request;
126 UserGroupInformation serviceUGI;
127 HttpKerberosServerAction(HttpServletRequest request, UserGroupInformation serviceUGI) {
128 this.request = request;
129 this.serviceUGI = serviceUGI;
130 }
131
132 @Override
133 public String run() throws HttpAuthenticationException {
134
135 GSSManager manager = GSSManager.getInstance();
136 GSSContext gssContext = null;
137 String serverPrincipal = SecurityUtil.getPrincipalWithoutRealm(serviceUGI.getUserName());
138 try {
139
140 Oid kerberosMechOid = new Oid("1.2.840.113554.1.2.2");
141
142 Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2");
143
144 Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
145
146 GSSName serverName = manager.createName(serverPrincipal, krb5PrincipalOid);
147
148 GSSCredential serverCreds = manager.createCredential(serverName,
149 GSSCredential.DEFAULT_LIFETIME,
150 new Oid[]{kerberosMechOid, spnegoMechOid},
151 GSSCredential.ACCEPT_ONLY);
152
153 gssContext = manager.createContext(serverCreds);
154
155 String serviceTicketBase64 = getAuthHeader(request);
156 byte[] inToken = Base64.decodeBase64(serviceTicketBase64.getBytes());
157 gssContext.acceptSecContext(inToken, 0, inToken.length);
158
159 if (!gssContext.isEstablished()) {
160 throw new HttpAuthenticationException("Kerberos authentication failed: " +
161 "unable to establish context with the service ticket " +
162 "provided by the client.");
163 }
164 return SecurityUtil.getUserFromPrincipal(gssContext.getSrcName().toString());
165 } catch (GSSException e) {
166 throw new HttpAuthenticationException("Kerberos authentication failed: ", e);
167 } finally {
168 if (gssContext != null) {
169 try {
170 gssContext.dispose();
171 } catch (GSSException e) {
172 LOG.warn("Error while disposing GSS Context", e);
173 }
174 }
175 }
176 }
177
178
179
180
181
182
183 private String getAuthHeader(HttpServletRequest request)
184 throws HttpAuthenticationException {
185 String authHeader = request.getHeader("Authorization");
186
187 if (authHeader == null || authHeader.isEmpty()) {
188 throw new HttpAuthenticationException("Authorization header received " +
189 "from the client is empty.");
190 }
191 String authHeaderBase64String;
192 int beginIndex = ("Negotiate ").length();
193 authHeaderBase64String = authHeader.substring(beginIndex);
194
195 if (authHeaderBase64String == null || authHeaderBase64String.isEmpty()) {
196 throw new HttpAuthenticationException("Authorization header received " +
197 "from the client does not contain any data.");
198 }
199 return authHeaderBase64String;
200 }
201 }
202 }