1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.http;
19
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InterruptedIOException;
23 import java.io.PrintStream;
24 import java.net.BindException;
25 import java.net.InetSocketAddress;
26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.net.URL;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.Enumeration;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 import javax.servlet.Filter;
38 import javax.servlet.FilterChain;
39 import javax.servlet.FilterConfig;
40 import javax.servlet.ServletContext;
41 import javax.servlet.ServletException;
42 import javax.servlet.ServletRequest;
43 import javax.servlet.ServletResponse;
44 import javax.servlet.http.HttpServlet;
45 import javax.servlet.http.HttpServletRequest;
46 import javax.servlet.http.HttpServletRequestWrapper;
47 import javax.servlet.http.HttpServletResponse;
48
49 import org.apache.commons.logging.Log;
50 import org.apache.commons.logging.LogFactory;
51 import org.apache.hadoop.HadoopIllegalArgumentException;
52 import org.apache.hadoop.hbase.classification.InterfaceAudience;
53 import org.apache.hadoop.hbase.classification.InterfaceStability;
54 import org.apache.hadoop.conf.Configuration;
55 import org.apache.hadoop.fs.CommonConfigurationKeys;
56 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
57 import org.apache.hadoop.hbase.http.conf.ConfServlet;
58 import org.apache.hadoop.hbase.http.jmx.JMXJsonServlet;
59 import org.apache.hadoop.hbase.http.log.LogLevel;
60 import org.apache.hadoop.hbase.util.Threads;
61 import org.apache.hadoop.security.SecurityUtil;
62 import org.apache.hadoop.security.UserGroupInformation;
63 import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
64 import org.apache.hadoop.security.authorize.AccessControlList;
65 import org.apache.hadoop.util.ReflectionUtils;
66 import org.apache.hadoop.util.Shell;
67 import org.mortbay.io.Buffer;
68 import org.mortbay.jetty.Connector;
69 import org.mortbay.jetty.Handler;
70 import org.mortbay.jetty.MimeTypes;
71 import org.mortbay.jetty.RequestLog;
72 import org.mortbay.jetty.Server;
73 import org.mortbay.jetty.handler.ContextHandler;
74 import org.mortbay.jetty.handler.ContextHandlerCollection;
75 import org.mortbay.jetty.handler.HandlerCollection;
76 import org.mortbay.jetty.handler.RequestLogHandler;
77 import org.mortbay.jetty.nio.SelectChannelConnector;
78 import org.mortbay.jetty.security.SslSocketConnector;
79 import org.mortbay.jetty.servlet.Context;
80 import org.mortbay.jetty.servlet.DefaultServlet;
81 import org.mortbay.jetty.servlet.FilterHolder;
82 import org.mortbay.jetty.servlet.FilterMapping;
83 import org.mortbay.jetty.servlet.ServletHandler;
84 import org.mortbay.jetty.servlet.ServletHolder;
85 import org.mortbay.jetty.webapp.WebAppContext;
86 import org.mortbay.thread.QueuedThreadPool;
87 import org.mortbay.util.MultiException;
88
89 import com.google.common.base.Preconditions;
90 import com.google.common.collect.Lists;
91 import com.sun.jersey.spi.container.servlet.ServletContainer;
92
93
94
95
96
97
98
99
100
101 @InterfaceAudience.Private
102 @InterfaceStability.Evolving
103 public class HttpServer implements FilterContainer {
104 public static final Log LOG = LogFactory.getLog(HttpServer.class);
105 private static final String EMPTY_STRING = "";
106
107 static final String FILTER_INITIALIZERS_PROPERTY
108 = "hbase.http.filter.initializers";
109 static final String HTTP_MAX_THREADS = "hbase.http.max.threads";
110
111 public static final String HTTP_UI_AUTHENTICATION = "hbase.security.authentication.ui";
112 static final String HTTP_AUTHENTICATION_PREFIX = "hbase.security.authentication.";
113 static final String HTTP_SPNEGO_AUTHENTICATION_PREFIX = HTTP_AUTHENTICATION_PREFIX
114 + "spnego.";
115 static final String HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX = "kerberos.principal";
116 public static final String HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY =
117 HTTP_SPNEGO_AUTHENTICATION_PREFIX + HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX;
118 static final String HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX = "kerberos.keytab";
119 public static final String HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY =
120 HTTP_SPNEGO_AUTHENTICATION_PREFIX + HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX;
121 static final String HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX = "kerberos.name.rules";
122 public static final String HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_KEY =
123 HTTP_SPNEGO_AUTHENTICATION_PREFIX + HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX;
124 static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX =
125 "signature.secret.file";
126 public static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_KEY =
127 HTTP_AUTHENTICATION_PREFIX + HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX;
128
129
130
131 public static final String CONF_CONTEXT_ATTRIBUTE = "hbase.conf";
132 public static final String ADMINS_ACL = "admins.acl";
133 public static final String BIND_ADDRESS = "bind.address";
134 public static final String SPNEGO_FILTER = "SpnegoFilter";
135 public static final String NO_CACHE_FILTER = "NoCacheFilter";
136 public static final String APP_DIR = "webapps";
137
138 private final AccessControlList adminsAcl;
139
140 protected final Server webServer;
141 protected String appDir;
142 protected String logDir;
143
144 private static class ListenerInfo {
145
146
147
148
149 private final boolean isManaged;
150 private final Connector listener;
151 private ListenerInfo(boolean isManaged, Connector listener) {
152 this.isManaged = isManaged;
153 this.listener = listener;
154 }
155 }
156
157 private final List<ListenerInfo> listeners = Lists.newArrayList();
158
159 protected final WebAppContext webAppContext;
160 protected final boolean findPort;
161 protected final Map<Context, Boolean> defaultContexts =
162 new HashMap<Context, Boolean>();
163 protected final List<String> filterNames = new ArrayList<String>();
164 static final String STATE_DESCRIPTION_ALIVE = " - alive";
165 static final String STATE_DESCRIPTION_NOT_LIVE = " - not live";
166
167
168
169
170 public static class Builder {
171 private ArrayList<URI> endpoints = Lists.newArrayList();
172 private Connector connector;
173 private Configuration conf;
174 private String[] pathSpecs;
175 private AccessControlList adminsAcl;
176 private boolean securityEnabled = false;
177 private String usernameConfKey;
178 private String keytabConfKey;
179 private boolean needsClientAuth;
180
181 private String hostName;
182 private String appDir = APP_DIR;
183 private String logDir;
184 private boolean findPort;
185
186 private String trustStore;
187 private String trustStorePassword;
188 private String trustStoreType;
189
190 private String keyStore;
191 private String keyStorePassword;
192 private String keyStoreType;
193
194
195 private String keyPassword;
196
197 private String kerberosNameRulesKey;
198 private String signatureSecretFileKey;
199
200 @Deprecated
201 private String name;
202 @Deprecated
203 private String bindAddress;
204 @Deprecated
205 private int port = -1;
206
207
208
209
210
211
212
213
214
215
216
217 public Builder addEndpoint(URI endpoint) {
218 endpoints.add(endpoint);
219 return this;
220 }
221
222
223
224
225
226
227 public Builder hostName(String hostName) {
228 this.hostName = hostName;
229 return this;
230 }
231
232 public Builder trustStore(String location, String password, String type) {
233 this.trustStore = location;
234 this.trustStorePassword = password;
235 this.trustStoreType = type;
236 return this;
237 }
238
239 public Builder keyStore(String location, String password, String type) {
240 this.keyStore = location;
241 this.keyStorePassword = password;
242 this.keyStoreType = type;
243 return this;
244 }
245
246 public Builder keyPassword(String password) {
247 this.keyPassword = password;
248 return this;
249 }
250
251
252
253
254
255 public Builder needsClientAuth(boolean value) {
256 this.needsClientAuth = value;
257 return this;
258 }
259
260
261
262
263 @Deprecated
264 public Builder setName(String name){
265 this.name = name;
266 return this;
267 }
268
269
270
271
272 @Deprecated
273 public Builder setBindAddress(String bindAddress){
274 this.bindAddress = bindAddress;
275 return this;
276 }
277
278
279
280
281 @Deprecated
282 public Builder setPort(int port) {
283 this.port = port;
284 return this;
285 }
286
287 public Builder setFindPort(boolean findPort) {
288 this.findPort = findPort;
289 return this;
290 }
291
292 public Builder setConf(Configuration conf) {
293 this.conf = conf;
294 return this;
295 }
296
297 public Builder setConnector(Connector connector) {
298 this.connector = connector;
299 return this;
300 }
301
302 public Builder setPathSpec(String[] pathSpec) {
303 this.pathSpecs = pathSpec;
304 return this;
305 }
306
307 public Builder setACL(AccessControlList acl) {
308 this.adminsAcl = acl;
309 return this;
310 }
311
312 public Builder setSecurityEnabled(boolean securityEnabled) {
313 this.securityEnabled = securityEnabled;
314 return this;
315 }
316
317 public Builder setUsernameConfKey(String usernameConfKey) {
318 this.usernameConfKey = usernameConfKey;
319 return this;
320 }
321
322 public Builder setKeytabConfKey(String keytabConfKey) {
323 this.keytabConfKey = keytabConfKey;
324 return this;
325 }
326
327 public Builder setKerberosNameRulesKey(String kerberosNameRulesKey) {
328 this.kerberosNameRulesKey = kerberosNameRulesKey;
329 return this;
330 }
331
332 public Builder setSignatureSecretFileKey(String signatureSecretFileKey) {
333 this.signatureSecretFileKey = signatureSecretFileKey;
334 return this;
335 }
336
337 public Builder setAppDir(String appDir) {
338 this.appDir = appDir;
339 return this;
340 }
341
342 public Builder setLogDir(String logDir) {
343 this.logDir = logDir;
344 return this;
345 }
346
347 public HttpServer build() throws IOException {
348
349
350 if (this.name == null) {
351 throw new HadoopIllegalArgumentException("name is not set");
352 }
353
354
355 if (bindAddress != null && port != -1) {
356 try {
357 endpoints.add(0, new URI("http", "", bindAddress, port, "", "", ""));
358 } catch (URISyntaxException e) {
359 throw new HadoopIllegalArgumentException("Invalid endpoint: "+ e);
360 }
361 }
362
363 if (endpoints.size() == 0 && connector == null) {
364 throw new HadoopIllegalArgumentException("No endpoints specified");
365 }
366
367 if (hostName == null) {
368 hostName = endpoints.size() == 0 ? connector.getHost() : endpoints.get(
369 0).getHost();
370 }
371
372 if (this.conf == null) {
373 conf = new Configuration();
374 }
375
376 HttpServer server = new HttpServer(this);
377
378 if (this.securityEnabled) {
379 server.initSpnego(conf, hostName, usernameConfKey, keytabConfKey, kerberosNameRulesKey,
380 signatureSecretFileKey);
381 }
382
383 if (connector != null) {
384 server.addUnmanagedListener(connector);
385 }
386
387 for (URI ep : endpoints) {
388 Connector listener = null;
389 String scheme = ep.getScheme();
390 if ("http".equals(scheme)) {
391 listener = HttpServer.createDefaultChannelConnector();
392 } else if ("https".equals(scheme)) {
393 SslSocketConnector c = new SslSocketConnectorSecure();
394 c.setNeedClientAuth(needsClientAuth);
395 c.setKeyPassword(keyPassword);
396
397 if (keyStore != null) {
398 c.setKeystore(keyStore);
399 c.setKeystoreType(keyStoreType);
400 c.setPassword(keyStorePassword);
401 }
402
403 if (trustStore != null) {
404 c.setTruststore(trustStore);
405 c.setTruststoreType(trustStoreType);
406 c.setTrustPassword(trustStorePassword);
407 }
408 listener = c;
409
410 } else {
411 throw new HadoopIllegalArgumentException(
412 "unknown scheme for endpoint:" + ep);
413 }
414 listener.setHeaderBufferSize(1024*64);
415 listener.setHost(ep.getHost());
416 listener.setPort(ep.getPort() == -1 ? 0 : ep.getPort());
417 server.addManagedListener(listener);
418 }
419
420 server.loadListeners();
421 return server;
422
423 }
424
425 }
426
427
428 @Deprecated
429 public HttpServer(String name, String bindAddress, int port, boolean findPort
430 ) throws IOException {
431 this(name, bindAddress, port, findPort, new Configuration());
432 }
433
434 @Deprecated
435 public HttpServer(String name, String bindAddress, int port,
436 boolean findPort, Configuration conf, Connector connector) throws IOException {
437 this(name, bindAddress, port, findPort, conf, null, connector, null);
438 }
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454 @Deprecated
455 public HttpServer(String name, String bindAddress, int port,
456 boolean findPort, Configuration conf, String[] pathSpecs) throws IOException {
457 this(name, bindAddress, port, findPort, conf, null, null, pathSpecs);
458 }
459
460
461
462
463
464
465
466
467
468
469 @Deprecated
470 public HttpServer(String name, String bindAddress, int port,
471 boolean findPort, Configuration conf) throws IOException {
472 this(name, bindAddress, port, findPort, conf, null, null, null);
473 }
474
475 @Deprecated
476 public HttpServer(String name, String bindAddress, int port,
477 boolean findPort, Configuration conf, AccessControlList adminsAcl)
478 throws IOException {
479 this(name, bindAddress, port, findPort, conf, adminsAcl, null, null);
480 }
481
482
483
484
485
486
487
488
489
490
491
492
493
494 @Deprecated
495 public HttpServer(String name, String bindAddress, int port,
496 boolean findPort, Configuration conf, AccessControlList adminsAcl,
497 Connector connector) throws IOException {
498 this(name, bindAddress, port, findPort, conf, adminsAcl, connector, null);
499 }
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515 @Deprecated
516 public HttpServer(String name, String bindAddress, int port,
517 boolean findPort, Configuration conf, AccessControlList adminsAcl,
518 Connector connector, String[] pathSpecs) throws IOException {
519 this(new Builder().setName(name)
520 .addEndpoint(URI.create("http://" + bindAddress + ":" + port))
521 .setFindPort(findPort).setConf(conf).setACL(adminsAcl)
522 .setConnector(connector).setPathSpec(pathSpecs));
523 }
524
525 private HttpServer(final Builder b) throws IOException {
526 this.appDir = b.appDir;
527 this.logDir = b.logDir;
528 final String appDir = getWebAppsPath(b.name);
529 this.webServer = new Server();
530 this.adminsAcl = b.adminsAcl;
531 this.webAppContext = createWebAppContext(b.name, b.conf, adminsAcl, appDir);
532 this.findPort = b.findPort;
533 initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs);
534 }
535
536 private void initializeWebServer(String name, String hostName,
537 Configuration conf, String[] pathSpecs)
538 throws FileNotFoundException, IOException {
539
540 Preconditions.checkNotNull(webAppContext);
541
542 int maxThreads = conf.getInt(HTTP_MAX_THREADS, -1);
543
544
545 QueuedThreadPool threadPool = maxThreads == -1 ? new QueuedThreadPool()
546 : new QueuedThreadPool(maxThreads);
547 threadPool.setDaemon(true);
548 webServer.setThreadPool(threadPool);
549
550 ContextHandlerCollection contexts = new ContextHandlerCollection();
551 RequestLog requestLog = HttpRequestLog.getRequestLog(name);
552
553 if (requestLog != null) {
554 RequestLogHandler requestLogHandler = new RequestLogHandler();
555 requestLogHandler.setRequestLog(requestLog);
556 HandlerCollection handlers = new HandlerCollection();
557 handlers.setHandlers(new Handler[] { requestLogHandler, contexts });
558 webServer.setHandler(handlers);
559 } else {
560 webServer.setHandler(contexts);
561 }
562
563 final String appDir = getWebAppsPath(name);
564
565 webServer.addHandler(webAppContext);
566
567 addDefaultApps(contexts, appDir, conf);
568
569 addGlobalFilter("safety", QuotingInputFilter.class.getName(), null);
570 Map<String, String> params = new HashMap<String, String>();
571 params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY"));
572 addGlobalFilter("clickjackingprevention",
573 ClickjackingPreventionFilter.class.getName(), params);
574 final FilterInitializer[] initializers = getFilterInitializers(conf);
575 if (initializers != null) {
576 conf = new Configuration(conf);
577 conf.set(BIND_ADDRESS, hostName);
578 for (FilterInitializer c : initializers) {
579 c.initFilter(this, conf);
580 }
581 }
582
583 addDefaultServlets();
584
585 if (pathSpecs != null) {
586 for (String path : pathSpecs) {
587 LOG.info("adding path spec: " + path);
588 addFilterPathMapping(path, webAppContext);
589 }
590 }
591 }
592
593 private void addUnmanagedListener(Connector connector) {
594 listeners.add(new ListenerInfo(false, connector));
595 }
596
597 private void addManagedListener(Connector connector) {
598 listeners.add(new ListenerInfo(true, connector));
599 }
600
601 private static WebAppContext createWebAppContext(String name,
602 Configuration conf, AccessControlList adminsAcl, final String appDir) {
603 WebAppContext ctx = new WebAppContext();
604 ctx.setDisplayName(name);
605 ctx.setContextPath("/");
606 ctx.setWar(appDir + "/" + name);
607 ctx.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
608 ctx.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
609 addNoCacheFilter(ctx);
610 return ctx;
611 }
612
613 private static void addNoCacheFilter(WebAppContext ctxt) {
614 defineFilter(ctxt, NO_CACHE_FILTER, NoCacheFilter.class.getName(),
615 Collections.<String, String> emptyMap(), new String[] { "/*" });
616 }
617
618
619
620
621
622
623 public Connector createBaseListener(Configuration conf) throws IOException {
624 return HttpServer.createDefaultChannelConnector();
625 }
626
627 @InterfaceAudience.Private
628 public static Connector createDefaultChannelConnector() {
629 SelectChannelConnector ret = new SelectChannelConnector();
630 ret.setLowResourceMaxIdleTime(10000);
631 ret.setAcceptQueueSize(128);
632 ret.setResolveNames(false);
633 ret.setUseDirectBuffers(false);
634 if(Shell.WINDOWS) {
635
636
637
638
639 ret.setReuseAddress(false);
640 }
641 return ret;
642 }
643
644
645 private static FilterInitializer[] getFilterInitializers(Configuration conf) {
646 if (conf == null) {
647 return null;
648 }
649
650 Class<?>[] classes = conf.getClasses(FILTER_INITIALIZERS_PROPERTY);
651 if (classes == null) {
652 return null;
653 }
654
655 FilterInitializer[] initializers = new FilterInitializer[classes.length];
656 for(int i = 0; i < classes.length; i++) {
657 initializers[i] = (FilterInitializer)ReflectionUtils.newInstance(
658 classes[i], conf);
659 }
660 return initializers;
661 }
662
663
664
665
666
667
668 protected void addDefaultApps(ContextHandlerCollection parent,
669 final String appDir, Configuration conf) throws IOException {
670
671 String logDir = this.logDir;
672 if (logDir == null) {
673 logDir = System.getProperty("hadoop.log.dir");
674 }
675 if (logDir != null) {
676 Context logContext = new Context(parent, "/logs");
677 logContext.setResourceBase(logDir);
678 logContext.addServlet(AdminAuthorizedServlet.class, "/*");
679 if (conf.getBoolean(
680 ServerConfigurationKeys.HBASE_JETTY_LOGS_SERVE_ALIASES,
681 ServerConfigurationKeys.DEFAULT_HBASE_JETTY_LOGS_SERVE_ALIASES)) {
682 @SuppressWarnings("unchecked")
683 Map<String, String> params = logContext.getInitParams();
684 params.put(
685 "org.mortbay.jetty.servlet.Default.aliases", "true");
686 }
687 logContext.setDisplayName("logs");
688 setContextAttributes(logContext, conf);
689 addNoCacheFilter(webAppContext);
690 defaultContexts.put(logContext, true);
691 }
692
693 Context staticContext = new Context(parent, "/static");
694 staticContext.setResourceBase(appDir + "/static");
695 staticContext.addServlet(DefaultServlet.class, "/*");
696 staticContext.setDisplayName("static");
697 setContextAttributes(staticContext, conf);
698 defaultContexts.put(staticContext, true);
699 }
700
701 private void setContextAttributes(Context context, Configuration conf) {
702 context.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
703 context.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
704 }
705
706
707
708
709 protected void addDefaultServlets() {
710
711 addServlet("stacks", "/stacks", StackServlet.class);
712 addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
713 addServlet("jmx", "/jmx", JMXJsonServlet.class);
714 addServlet("conf", "/conf", ConfServlet.class);
715 try {
716 Class<? extends HttpServlet> clazz = (Class<? extends HttpServlet>)
717 Class.forName("org.apache.hadoop.metrics.MetricsServlet");
718 addServlet("metrics", "/metrics", clazz);
719 } catch (Exception e) {
720 LOG.warn("MetricsServlet class not found, metrics servlet will not start", e);
721 }
722 }
723
724 public void addContext(Context ctxt, boolean isFiltered)
725 throws IOException {
726 webServer.addHandler(ctxt);
727 addNoCacheFilter(webAppContext);
728 defaultContexts.put(ctxt, isFiltered);
729 }
730
731
732
733
734
735
736
737
738 protected void addContext(String pathSpec, String dir, boolean isFiltered) throws IOException {
739 if (0 == webServer.getHandlers().length) {
740 throw new RuntimeException("Couldn't find handler");
741 }
742 WebAppContext webAppCtx = new WebAppContext();
743 webAppCtx.setContextPath(pathSpec);
744 webAppCtx.setWar(dir);
745 addContext(webAppCtx, true);
746 }
747
748
749
750
751
752
753
754 public void setAttribute(String name, Object value) {
755 webAppContext.setAttribute(name, value);
756 }
757
758
759
760
761
762
763 public void addJerseyResourcePackage(final String packageName,
764 final String pathSpec) {
765 LOG.info("addJerseyResourcePackage: packageName=" + packageName
766 + ", pathSpec=" + pathSpec);
767 final ServletHolder sh = new ServletHolder(ServletContainer.class);
768 sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
769 "com.sun.jersey.api.core.PackagesResourceConfig");
770 sh.setInitParameter("com.sun.jersey.config.property.packages", packageName);
771 webAppContext.addServlet(sh, pathSpec);
772 }
773
774
775
776
777
778
779
780 public void addServlet(String name, String pathSpec,
781 Class<? extends HttpServlet> clazz) {
782 addInternalServlet(name, pathSpec, clazz, false);
783 addFilterPathMapping(pathSpec, webAppContext);
784 }
785
786
787
788
789
790
791
792
793
794
795
796 public void addInternalServlet(String name, String pathSpec,
797 Class<? extends HttpServlet> clazz) {
798 addInternalServlet(name, pathSpec, clazz, false);
799 }
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814 public void addInternalServlet(String name, String pathSpec,
815 Class<? extends HttpServlet> clazz, boolean requireAuth) {
816 ServletHolder holder = new ServletHolder(clazz);
817 if (name != null) {
818 holder.setName(name);
819 }
820 webAppContext.addServlet(holder, pathSpec);
821
822 if(requireAuth && UserGroupInformation.isSecurityEnabled()) {
823 LOG.info("Adding Kerberos (SPNEGO) filter to " + name);
824 ServletHandler handler = webAppContext.getServletHandler();
825 FilterMapping fmap = new FilterMapping();
826 fmap.setPathSpec(pathSpec);
827 fmap.setFilterName(SPNEGO_FILTER);
828 fmap.setDispatches(Handler.ALL);
829 handler.addFilterMapping(fmap);
830 }
831 }
832
833 @Override
834 public void addFilter(String name, String classname,
835 Map<String, String> parameters) {
836
837 final String[] USER_FACING_URLS = { "*.html", "*.jsp" };
838 defineFilter(webAppContext, name, classname, parameters, USER_FACING_URLS);
839 LOG.info("Added filter " + name + " (class=" + classname
840 + ") to context " + webAppContext.getDisplayName());
841 final String[] ALL_URLS = { "/*" };
842 for (Map.Entry<Context, Boolean> e : defaultContexts.entrySet()) {
843 if (e.getValue()) {
844 Context ctx = e.getKey();
845 defineFilter(ctx, name, classname, parameters, ALL_URLS);
846 LOG.info("Added filter " + name + " (class=" + classname
847 + ") to context " + ctx.getDisplayName());
848 }
849 }
850 filterNames.add(name);
851 }
852
853 @Override
854 public void addGlobalFilter(String name, String classname,
855 Map<String, String> parameters) {
856 final String[] ALL_URLS = { "/*" };
857 defineFilter(webAppContext, name, classname, parameters, ALL_URLS);
858 for (Context ctx : defaultContexts.keySet()) {
859 defineFilter(ctx, name, classname, parameters, ALL_URLS);
860 }
861 LOG.info("Added global filter '" + name + "' (class=" + classname + ")");
862 }
863
864
865
866
867 public static void defineFilter(Context ctx, String name,
868 String classname, Map<String,String> parameters, String[] urls) {
869
870 FilterHolder holder = new FilterHolder();
871 holder.setName(name);
872 holder.setClassName(classname);
873 holder.setInitParameters(parameters);
874 FilterMapping fmap = new FilterMapping();
875 fmap.setPathSpecs(urls);
876 fmap.setDispatches(Handler.ALL);
877 fmap.setFilterName(name);
878 ServletHandler handler = ctx.getServletHandler();
879 handler.addFilter(holder, fmap);
880 }
881
882
883
884
885
886
887 protected void addFilterPathMapping(String pathSpec,
888 Context webAppCtx) {
889 ServletHandler handler = webAppCtx.getServletHandler();
890 for(String name : filterNames) {
891 FilterMapping fmap = new FilterMapping();
892 fmap.setPathSpec(pathSpec);
893 fmap.setFilterName(name);
894 fmap.setDispatches(Handler.ALL);
895 handler.addFilterMapping(fmap);
896 }
897 }
898
899
900
901
902
903
904 public Object getAttribute(String name) {
905 return webAppContext.getAttribute(name);
906 }
907
908 public WebAppContext getWebAppContext(){
909 return this.webAppContext;
910 }
911
912 public String getWebAppsPath(String appName) throws FileNotFoundException {
913 return getWebAppsPath(this.appDir, appName);
914 }
915
916
917
918
919
920
921
922 protected String getWebAppsPath(String webapps, String appName) throws FileNotFoundException {
923 URL url = getClass().getClassLoader().getResource(webapps + "/" + appName);
924 if (url == null)
925 throw new FileNotFoundException(webapps + "/" + appName
926 + " not found in CLASSPATH");
927 String urlString = url.toString();
928 return urlString.substring(0, urlString.lastIndexOf('/'));
929 }
930
931
932
933
934
935 @Deprecated
936 public int getPort() {
937 return webServer.getConnectors()[0].getLocalPort();
938 }
939
940
941
942
943
944
945
946 public InetSocketAddress getConnectorAddress(int index) {
947 Preconditions.checkArgument(index >= 0);
948 if (index > webServer.getConnectors().length)
949 return null;
950
951 Connector c = webServer.getConnectors()[index];
952 if (c.getLocalPort() == -1) {
953
954 return null;
955 }
956
957 return new InetSocketAddress(c.getHost(), c.getLocalPort());
958 }
959
960
961
962
963 public void setThreads(int min, int max) {
964 QueuedThreadPool pool = (QueuedThreadPool) webServer.getThreadPool();
965 pool.setMinThreads(min);
966 pool.setMaxThreads(max);
967 }
968
969 private void initSpnego(Configuration conf, String hostName,
970 String usernameConfKey, String keytabConfKey, String kerberosNameRuleKey,
971 String signatureSecretKeyFileKey) throws IOException {
972 Map<String, String> params = new HashMap<String, String>();
973 String principalInConf = getOrEmptyString(conf, usernameConfKey);
974 if (!principalInConf.isEmpty()) {
975 params.put(HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX, SecurityUtil.getServerPrincipal(
976 principalInConf, hostName));
977 }
978 String httpKeytab = getOrEmptyString(conf, keytabConfKey);
979 if (!httpKeytab.isEmpty()) {
980 params.put(HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX, httpKeytab);
981 }
982 String kerberosNameRule = getOrEmptyString(conf, kerberosNameRuleKey);
983 if (!kerberosNameRule.isEmpty()) {
984 params.put(HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX, kerberosNameRule);
985 }
986 String signatureSecretKeyFile = getOrEmptyString(conf, signatureSecretKeyFileKey);
987 if (!signatureSecretKeyFile.isEmpty()) {
988 params.put(HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX,
989 signatureSecretKeyFile);
990 }
991 params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
992
993
994 if (isMissing(params.get(HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX)) ||
995 isMissing(params.get(HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX))) {
996 throw new IllegalArgumentException(usernameConfKey + " and "
997 + keytabConfKey + " are both required in the configuration "
998 + "to enable SPNEGO/Kerberos authentication for the Web UI");
999 }
1000
1001 addGlobalFilter(SPNEGO_FILTER, AuthenticationFilter.class.getName(), params);
1002 }
1003
1004
1005
1006
1007 private boolean isMissing(String value) {
1008 if (null == value) {
1009 return true;
1010 }
1011 return value.trim().isEmpty();
1012 }
1013
1014
1015
1016
1017
1018 private String getOrEmptyString(Configuration conf, String key) {
1019 if (null == key) {
1020 return EMPTY_STRING;
1021 }
1022 final String value = conf.get(key.trim());
1023 return null == value ? EMPTY_STRING : value;
1024 }
1025
1026
1027
1028
1029 public void start() throws IOException {
1030 try {
1031 try {
1032 openListeners();
1033 webServer.start();
1034 } catch (IOException ex) {
1035 LOG.info("HttpServer.start() threw a non Bind IOException", ex);
1036 throw ex;
1037 } catch (MultiException ex) {
1038 LOG.info("HttpServer.start() threw a MultiException", ex);
1039 throw ex;
1040 }
1041
1042 Handler[] handlers = webServer.getHandlers();
1043 for (int i = 0; i < handlers.length; i++) {
1044 if (handlers[i].isFailed()) {
1045 throw new IOException(
1046 "Problem in starting http server. Server handlers failed");
1047 }
1048 }
1049
1050 Throwable unavailableException = webAppContext.getUnavailableException();
1051 if (unavailableException != null) {
1052
1053
1054 webServer.stop();
1055 throw new IOException("Unable to initialize WebAppContext",
1056 unavailableException);
1057 }
1058 } catch (IOException e) {
1059 throw e;
1060 } catch (InterruptedException e) {
1061 throw (IOException) new InterruptedIOException(
1062 "Interrupted while starting HTTP server").initCause(e);
1063 } catch (Exception e) {
1064 throw new IOException("Problem starting http server", e);
1065 }
1066 }
1067
1068 private void loadListeners() {
1069 for (ListenerInfo li : listeners) {
1070 webServer.addConnector(li.listener);
1071 }
1072 }
1073
1074
1075
1076
1077
1078 void openListeners() throws Exception {
1079 for (ListenerInfo li : listeners) {
1080 Connector listener = li.listener;
1081 if (!li.isManaged || li.listener.getLocalPort() != -1) {
1082
1083 continue;
1084 }
1085 int port = listener.getPort();
1086 while (true) {
1087
1088
1089 try {
1090 listener.close();
1091 listener.open();
1092 LOG.info("Jetty bound to port " + listener.getLocalPort());
1093 break;
1094 } catch (BindException ex) {
1095 if (port == 0 || !findPort) {
1096 BindException be = new BindException("Port in use: "
1097 + listener.getHost() + ":" + listener.getPort());
1098 be.initCause(ex);
1099 throw be;
1100 }
1101 }
1102
1103 listener.setPort(++port);
1104 Thread.sleep(100);
1105 }
1106 }
1107 }
1108
1109
1110
1111
1112 public void stop() throws Exception {
1113 MultiException exception = null;
1114 for (ListenerInfo li : listeners) {
1115 if (!li.isManaged) {
1116 continue;
1117 }
1118
1119 try {
1120 li.listener.close();
1121 } catch (Exception e) {
1122 LOG.error(
1123 "Error while stopping listener for webapp"
1124 + webAppContext.getDisplayName(), e);
1125 exception = addMultiException(exception, e);
1126 }
1127 }
1128
1129 try {
1130
1131 webAppContext.clearAttributes();
1132 webAppContext.stop();
1133 } catch (Exception e) {
1134 LOG.error("Error while stopping web app context for webapp "
1135 + webAppContext.getDisplayName(), e);
1136 exception = addMultiException(exception, e);
1137 }
1138
1139 try {
1140 webServer.stop();
1141 } catch (Exception e) {
1142 LOG.error("Error while stopping web server for webapp "
1143 + webAppContext.getDisplayName(), e);
1144 exception = addMultiException(exception, e);
1145 }
1146
1147 if (exception != null) {
1148 exception.ifExceptionThrow();
1149 }
1150
1151 }
1152
1153 private MultiException addMultiException(MultiException exception, Exception e) {
1154 if(exception == null){
1155 exception = new MultiException();
1156 }
1157 exception.add(e);
1158 return exception;
1159 }
1160
1161 public void join() throws InterruptedException {
1162 webServer.join();
1163 }
1164
1165
1166
1167
1168
1169 public boolean isAlive() {
1170 return webServer != null && webServer.isStarted();
1171 }
1172
1173
1174
1175
1176
1177 @Override
1178 public String toString() {
1179 if (listeners.size() == 0) {
1180 return "Inactive HttpServer";
1181 } else {
1182 StringBuilder sb = new StringBuilder("HttpServer (")
1183 .append(isAlive() ? STATE_DESCRIPTION_ALIVE : STATE_DESCRIPTION_NOT_LIVE).append("), listening at:");
1184 for (ListenerInfo li : listeners) {
1185 Connector l = li.listener;
1186 sb.append(l.getHost()).append(":").append(l.getPort()).append("/,");
1187 }
1188 return sb.toString();
1189 }
1190 }
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207 public static boolean isInstrumentationAccessAllowed(
1208 ServletContext servletContext, HttpServletRequest request,
1209 HttpServletResponse response) throws IOException {
1210 Configuration conf =
1211 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1212
1213 boolean access = true;
1214 boolean adminAccess = conf.getBoolean(
1215 CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN,
1216 false);
1217 if (adminAccess) {
1218 access = hasAdministratorAccess(servletContext, request, response);
1219 }
1220 return access;
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233 public static boolean hasAdministratorAccess(
1234 ServletContext servletContext, HttpServletRequest request,
1235 HttpServletResponse response) throws IOException {
1236 Configuration conf =
1237 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1238
1239 if (!conf.getBoolean(
1240 CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
1241 return true;
1242 }
1243
1244 String remoteUser = request.getRemoteUser();
1245 if (remoteUser == null) {
1246 response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
1247 "Unauthenticated users are not " +
1248 "authorized to access this page.");
1249 return false;
1250 }
1251
1252 if (servletContext.getAttribute(ADMINS_ACL) != null &&
1253 !userHasAdministratorAccess(servletContext, remoteUser)) {
1254 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User "
1255 + remoteUser + " is unauthorized to access this page.");
1256 return false;
1257 }
1258
1259 return true;
1260 }
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271 public static boolean userHasAdministratorAccess(ServletContext servletContext,
1272 String remoteUser) {
1273 AccessControlList adminsAcl = (AccessControlList) servletContext
1274 .getAttribute(ADMINS_ACL);
1275 UserGroupInformation remoteUserUGI =
1276 UserGroupInformation.createRemoteUser(remoteUser);
1277 return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
1278 }
1279
1280
1281
1282
1283
1284
1285
1286 public static class StackServlet extends HttpServlet {
1287 private static final long serialVersionUID = -6284183679759467039L;
1288
1289 @Override
1290 public void doGet(HttpServletRequest request, HttpServletResponse response)
1291 throws ServletException, IOException {
1292 if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(),
1293 request, response)) {
1294 return;
1295 }
1296 response.setContentType("text/plain; charset=UTF-8");
1297 try (PrintStream out = new PrintStream(
1298 response.getOutputStream(), false, "UTF-8")) {
1299 Threads.printThreadInfo(out, "");
1300 out.flush();
1301 }
1302 ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
1303 }
1304 }
1305
1306
1307
1308
1309
1310
1311 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
1312 public static class QuotingInputFilter implements Filter {
1313 private FilterConfig config;
1314
1315 public static class RequestQuoter extends HttpServletRequestWrapper {
1316 private final HttpServletRequest rawRequest;
1317 public RequestQuoter(HttpServletRequest rawRequest) {
1318 super(rawRequest);
1319 this.rawRequest = rawRequest;
1320 }
1321
1322
1323
1324
1325 @SuppressWarnings("unchecked")
1326 @Override
1327 public Enumeration<String> getParameterNames() {
1328 return new Enumeration<String>() {
1329 private Enumeration<String> rawIterator =
1330 rawRequest.getParameterNames();
1331 @Override
1332 public boolean hasMoreElements() {
1333 return rawIterator.hasMoreElements();
1334 }
1335
1336 @Override
1337 public String nextElement() {
1338 return HtmlQuoting.quoteHtmlChars(rawIterator.nextElement());
1339 }
1340 };
1341 }
1342
1343
1344
1345
1346 @Override
1347 public String getParameter(String name) {
1348 return HtmlQuoting.quoteHtmlChars(rawRequest.getParameter
1349 (HtmlQuoting.unquoteHtmlChars(name)));
1350 }
1351
1352 @Override
1353 public String[] getParameterValues(String name) {
1354 String unquoteName = HtmlQuoting.unquoteHtmlChars(name);
1355 String[] unquoteValue = rawRequest.getParameterValues(unquoteName);
1356 if (unquoteValue == null) {
1357 return null;
1358 }
1359 String[] result = new String[unquoteValue.length];
1360 for(int i=0; i < result.length; ++i) {
1361 result[i] = HtmlQuoting.quoteHtmlChars(unquoteValue[i]);
1362 }
1363 return result;
1364 }
1365
1366 @SuppressWarnings("unchecked")
1367 @Override
1368 public Map<String, String[]> getParameterMap() {
1369 Map<String, String[]> result = new HashMap<String,String[]>();
1370 Map<String, String[]> raw = rawRequest.getParameterMap();
1371 for (Map.Entry<String,String[]> item: raw.entrySet()) {
1372 String[] rawValue = item.getValue();
1373 String[] cookedValue = new String[rawValue.length];
1374 for(int i=0; i< rawValue.length; ++i) {
1375 cookedValue[i] = HtmlQuoting.quoteHtmlChars(rawValue[i]);
1376 }
1377 result.put(HtmlQuoting.quoteHtmlChars(item.getKey()), cookedValue);
1378 }
1379 return result;
1380 }
1381
1382
1383
1384
1385
1386 @Override
1387 public StringBuffer getRequestURL(){
1388 String url = rawRequest.getRequestURL().toString();
1389 return new StringBuffer(HtmlQuoting.quoteHtmlChars(url));
1390 }
1391
1392
1393
1394
1395
1396 @Override
1397 public String getServerName() {
1398 return HtmlQuoting.quoteHtmlChars(rawRequest.getServerName());
1399 }
1400 }
1401
1402 @Override
1403 public void init(FilterConfig config) throws ServletException {
1404 this.config = config;
1405 }
1406
1407 @Override
1408 public void destroy() {
1409 }
1410
1411 @Override
1412 public void doFilter(ServletRequest request,
1413 ServletResponse response,
1414 FilterChain chain
1415 ) throws IOException, ServletException {
1416 HttpServletRequestWrapper quoted =
1417 new RequestQuoter((HttpServletRequest) request);
1418 HttpServletResponse httpResponse = (HttpServletResponse) response;
1419
1420 String mime = inferMimeType(request);
1421 if (mime == null) {
1422 httpResponse.setContentType("text/plain; charset=utf-8");
1423 } else if (mime.startsWith("text/html")) {
1424
1425
1426
1427
1428 httpResponse.setContentType("text/html; charset=utf-8");
1429 } else if (mime.startsWith("application/xml")) {
1430 httpResponse.setContentType("text/xml; charset=utf-8");
1431 }
1432 chain.doFilter(quoted, httpResponse);
1433 }
1434
1435
1436
1437
1438
1439 private String inferMimeType(ServletRequest request) {
1440 String path = ((HttpServletRequest)request).getRequestURI();
1441 ContextHandler.SContext sContext = (ContextHandler.SContext)config.getServletContext();
1442 MimeTypes mimes = sContext.getContextHandler().getMimeTypes();
1443 Buffer mimeBuffer = mimes.getMimeByExtension(path);
1444 return (mimeBuffer == null) ? null : mimeBuffer.toString();
1445 }
1446
1447 }
1448
1449 }