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