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.hbase.util.ReflectionUtils;
62 import org.apache.hadoop.security.SecurityUtil;
63 import org.apache.hadoop.security.UserGroupInformation;
64 import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
65 import org.apache.hadoop.security.authorize.AccessControlList;
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 private 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
609 ctx.getServletContext().setAttribute(
610 org.apache.hadoop.http.HttpServer2.CONF_CONTEXT_ATTRIBUTE, conf);
611 ctx.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
612 addNoCacheFilter(ctx);
613 return ctx;
614 }
615
616 private static void addNoCacheFilter(WebAppContext ctxt) {
617 defineFilter(ctxt, NO_CACHE_FILTER, NoCacheFilter.class.getName(),
618 Collections.<String, String> emptyMap(), new String[] { "/*" });
619 }
620
621
622
623
624
625
626 public Connector createBaseListener(Configuration conf) throws IOException {
627 return HttpServer.createDefaultChannelConnector();
628 }
629
630 @InterfaceAudience.Private
631 public static Connector createDefaultChannelConnector() {
632 SelectChannelConnector ret = new SelectChannelConnector();
633 ret.setLowResourceMaxIdleTime(10000);
634 ret.setAcceptQueueSize(128);
635 ret.setResolveNames(false);
636 ret.setUseDirectBuffers(false);
637 if(Shell.WINDOWS) {
638
639
640
641
642 ret.setReuseAddress(false);
643 }
644 return ret;
645 }
646
647
648 private static FilterInitializer[] getFilterInitializers(Configuration conf) {
649 if (conf == null) {
650 return null;
651 }
652
653 Class<?>[] classes = conf.getClasses(FILTER_INITIALIZERS_PROPERTY);
654 if (classes == null) {
655 return null;
656 }
657
658 FilterInitializer[] initializers = new FilterInitializer[classes.length];
659 for(int i = 0; i < classes.length; i++) {
660 initializers[i] = (FilterInitializer)ReflectionUtils.newInstance(classes[i]);
661 }
662 return initializers;
663 }
664
665
666
667
668
669
670 protected void addDefaultApps(ContextHandlerCollection parent,
671 final String appDir, Configuration conf) throws IOException {
672
673 String logDir = this.logDir;
674 if (logDir == null) {
675 logDir = System.getProperty("hadoop.log.dir");
676 }
677 if (logDir != null) {
678 Context logContext = new Context(parent, "/logs");
679 logContext.setResourceBase(logDir);
680 logContext.addServlet(AdminAuthorizedServlet.class, "/*");
681 if (conf.getBoolean(
682 ServerConfigurationKeys.HBASE_JETTY_LOGS_SERVE_ALIASES,
683 ServerConfigurationKeys.DEFAULT_HBASE_JETTY_LOGS_SERVE_ALIASES)) {
684 @SuppressWarnings("unchecked")
685 Map<String, String> params = logContext.getInitParams();
686 params.put(
687 "org.mortbay.jetty.servlet.Default.aliases", "true");
688 }
689 logContext.setDisplayName("logs");
690 setContextAttributes(logContext, conf);
691 addNoCacheFilter(webAppContext);
692 defaultContexts.put(logContext, true);
693 }
694
695 Context staticContext = new Context(parent, "/static");
696 staticContext.setResourceBase(appDir + "/static");
697 staticContext.addServlet(DefaultServlet.class, "/*");
698 staticContext.setDisplayName("static");
699 setContextAttributes(staticContext, conf);
700 defaultContexts.put(staticContext, true);
701 }
702
703 private void setContextAttributes(Context context, Configuration conf) {
704 context.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
705 context.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
706 }
707
708
709
710
711 protected void addDefaultServlets() {
712
713 addServlet("stacks", "/stacks", StackServlet.class);
714 addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
715 addServlet("jmx", "/jmx", JMXJsonServlet.class);
716 addServlet("conf", "/conf", ConfServlet.class);
717 try {
718 Class<? extends HttpServlet> clazz = (Class<? extends HttpServlet>)
719 Class.forName("org.apache.hadoop.metrics.MetricsServlet");
720 addServlet("metrics", "/metrics", clazz);
721 } catch (Exception e) {
722 LOG.warn("MetricsServlet class not found, metrics servlet will not start", e);
723 }
724 }
725
726 public void addContext(Context ctxt, boolean isFiltered)
727 throws IOException {
728 webServer.addHandler(ctxt);
729 addNoCacheFilter(webAppContext);
730 defaultContexts.put(ctxt, isFiltered);
731 }
732
733
734
735
736
737
738
739
740 protected void addContext(String pathSpec, String dir, boolean isFiltered) throws IOException {
741 if (0 == webServer.getHandlers().length) {
742 throw new RuntimeException("Couldn't find handler");
743 }
744 WebAppContext webAppCtx = new WebAppContext();
745 webAppCtx.setContextPath(pathSpec);
746 webAppCtx.setWar(dir);
747 addContext(webAppCtx, true);
748 }
749
750
751
752
753
754
755
756 public void setAttribute(String name, Object value) {
757 webAppContext.setAttribute(name, value);
758 }
759
760
761
762
763
764
765 public void addJerseyResourcePackage(final String packageName,
766 final String pathSpec) {
767 LOG.info("addJerseyResourcePackage: packageName=" + packageName
768 + ", pathSpec=" + pathSpec);
769 final ServletHolder sh = new ServletHolder(ServletContainer.class);
770 sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
771 "com.sun.jersey.api.core.PackagesResourceConfig");
772 sh.setInitParameter("com.sun.jersey.config.property.packages", packageName);
773 webAppContext.addServlet(sh, pathSpec);
774 }
775
776
777
778
779
780
781
782 public void addServlet(String name, String pathSpec,
783 Class<? extends HttpServlet> clazz) {
784 addInternalServlet(name, pathSpec, clazz, false);
785 addFilterPathMapping(pathSpec, webAppContext);
786 }
787
788
789
790
791
792
793
794
795
796
797
798 public void addInternalServlet(String name, String pathSpec,
799 Class<? extends HttpServlet> clazz) {
800 addInternalServlet(name, pathSpec, clazz, false);
801 }
802
803
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, boolean requireAuth) {
818 ServletHolder holder = new ServletHolder(clazz);
819 if (name != null) {
820 holder.setName(name);
821 }
822 webAppContext.addServlet(holder, pathSpec);
823
824 if(requireAuth && UserGroupInformation.isSecurityEnabled()) {
825 LOG.info("Adding Kerberos (SPNEGO) filter to " + name);
826 ServletHandler handler = webAppContext.getServletHandler();
827 FilterMapping fmap = new FilterMapping();
828 fmap.setPathSpec(pathSpec);
829 fmap.setFilterName(SPNEGO_FILTER);
830 fmap.setDispatches(Handler.ALL);
831 handler.addFilterMapping(fmap);
832 }
833 }
834
835 @Override
836 public void addFilter(String name, String classname,
837 Map<String, String> parameters) {
838
839 final String[] USER_FACING_URLS = { "*.html", "*.jsp" };
840 defineFilter(webAppContext, name, classname, parameters, USER_FACING_URLS);
841 LOG.info("Added filter " + name + " (class=" + classname
842 + ") to context " + webAppContext.getDisplayName());
843 final String[] ALL_URLS = { "/*" };
844 for (Map.Entry<Context, Boolean> e : defaultContexts.entrySet()) {
845 if (e.getValue()) {
846 Context ctx = e.getKey();
847 defineFilter(ctx, name, classname, parameters, ALL_URLS);
848 LOG.info("Added filter " + name + " (class=" + classname
849 + ") to context " + ctx.getDisplayName());
850 }
851 }
852 filterNames.add(name);
853 }
854
855 @Override
856 public void addGlobalFilter(String name, String classname,
857 Map<String, String> parameters) {
858 final String[] ALL_URLS = { "/*" };
859 defineFilter(webAppContext, name, classname, parameters, ALL_URLS);
860 for (Context ctx : defaultContexts.keySet()) {
861 defineFilter(ctx, name, classname, parameters, ALL_URLS);
862 }
863 LOG.info("Added global filter '" + name + "' (class=" + classname + ")");
864 }
865
866
867
868
869 public static void defineFilter(Context ctx, String name,
870 String classname, Map<String,String> parameters, String[] urls) {
871
872 FilterHolder holder = new FilterHolder();
873 holder.setName(name);
874 holder.setClassName(classname);
875 holder.setInitParameters(parameters);
876 FilterMapping fmap = new FilterMapping();
877 fmap.setPathSpecs(urls);
878 fmap.setDispatches(Handler.ALL);
879 fmap.setFilterName(name);
880 ServletHandler handler = ctx.getServletHandler();
881 handler.addFilter(holder, fmap);
882 }
883
884
885
886
887
888
889 protected void addFilterPathMapping(String pathSpec,
890 Context webAppCtx) {
891 ServletHandler handler = webAppCtx.getServletHandler();
892 for(String name : filterNames) {
893 FilterMapping fmap = new FilterMapping();
894 fmap.setPathSpec(pathSpec);
895 fmap.setFilterName(name);
896 fmap.setDispatches(Handler.ALL);
897 handler.addFilterMapping(fmap);
898 }
899 }
900
901
902
903
904
905
906 public Object getAttribute(String name) {
907 return webAppContext.getAttribute(name);
908 }
909
910 public WebAppContext getWebAppContext(){
911 return this.webAppContext;
912 }
913
914 public String getWebAppsPath(String appName) throws FileNotFoundException {
915 return getWebAppsPath(this.appDir, appName);
916 }
917
918
919
920
921
922
923
924 protected String getWebAppsPath(String webapps, String appName) throws FileNotFoundException {
925 URL url = getClass().getClassLoader().getResource(webapps + "/" + appName);
926 if (url == null)
927 throw new FileNotFoundException(webapps + "/" + appName
928 + " not found in CLASSPATH");
929 String urlString = url.toString();
930 return urlString.substring(0, urlString.lastIndexOf('/'));
931 }
932
933
934
935
936
937 @Deprecated
938 public int getPort() {
939 return webServer.getConnectors()[0].getLocalPort();
940 }
941
942
943
944
945
946
947
948 public InetSocketAddress getConnectorAddress(int index) {
949 Preconditions.checkArgument(index >= 0);
950 if (index > webServer.getConnectors().length)
951 return null;
952
953 Connector c = webServer.getConnectors()[index];
954 if (c.getLocalPort() == -1) {
955
956 return null;
957 }
958
959 return new InetSocketAddress(c.getHost(), c.getLocalPort());
960 }
961
962
963
964
965 public void setThreads(int min, int max) {
966 QueuedThreadPool pool = (QueuedThreadPool) webServer.getThreadPool();
967 pool.setMinThreads(min);
968 pool.setMaxThreads(max);
969 }
970
971 private void initSpnego(Configuration conf, String hostName,
972 String usernameConfKey, String keytabConfKey, String kerberosNameRuleKey,
973 String signatureSecretKeyFileKey) throws IOException {
974 Map<String, String> params = new HashMap<String, String>();
975 String principalInConf = getOrEmptyString(conf, usernameConfKey);
976 if (!principalInConf.isEmpty()) {
977 params.put(HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX, SecurityUtil.getServerPrincipal(
978 principalInConf, hostName));
979 }
980 String httpKeytab = getOrEmptyString(conf, keytabConfKey);
981 if (!httpKeytab.isEmpty()) {
982 params.put(HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX, httpKeytab);
983 }
984 String kerberosNameRule = getOrEmptyString(conf, kerberosNameRuleKey);
985 if (!kerberosNameRule.isEmpty()) {
986 params.put(HTTP_SPNEGO_AUTHENTICATION_KRB_NAME_SUFFIX, kerberosNameRule);
987 }
988 String signatureSecretKeyFile = getOrEmptyString(conf, signatureSecretKeyFileKey);
989 if (!signatureSecretKeyFile.isEmpty()) {
990 params.put(HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX,
991 signatureSecretKeyFile);
992 }
993 params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
994
995
996 if (isMissing(params.get(HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_SUFFIX)) ||
997 isMissing(params.get(HTTP_SPNEGO_AUTHENTICATION_KEYTAB_SUFFIX))) {
998 throw new IllegalArgumentException(usernameConfKey + " and "
999 + keytabConfKey + " are both required in the configuration "
1000 + "to enable SPNEGO/Kerberos authentication for the Web UI");
1001 }
1002
1003 addGlobalFilter(SPNEGO_FILTER, AuthenticationFilter.class.getName(), params);
1004 }
1005
1006
1007
1008
1009 private boolean isMissing(String value) {
1010 if (null == value) {
1011 return true;
1012 }
1013 return value.trim().isEmpty();
1014 }
1015
1016
1017
1018
1019
1020 private String getOrEmptyString(Configuration conf, String key) {
1021 if (null == key) {
1022 return EMPTY_STRING;
1023 }
1024 final String value = conf.get(key.trim());
1025 return null == value ? EMPTY_STRING : value;
1026 }
1027
1028
1029
1030
1031 public void start() throws IOException {
1032 try {
1033 try {
1034 openListeners();
1035 webServer.start();
1036 } catch (IOException ex) {
1037 LOG.info("HttpServer.start() threw a non Bind IOException", ex);
1038 throw ex;
1039 } catch (MultiException ex) {
1040 LOG.info("HttpServer.start() threw a MultiException", ex);
1041 throw ex;
1042 }
1043
1044 Handler[] handlers = webServer.getHandlers();
1045 for (int i = 0; i < handlers.length; i++) {
1046 if (handlers[i].isFailed()) {
1047 throw new IOException(
1048 "Problem in starting http server. Server handlers failed");
1049 }
1050 }
1051
1052 Throwable unavailableException = webAppContext.getUnavailableException();
1053 if (unavailableException != null) {
1054
1055
1056 webServer.stop();
1057 throw new IOException("Unable to initialize WebAppContext",
1058 unavailableException);
1059 }
1060 } catch (IOException e) {
1061 throw e;
1062 } catch (InterruptedException e) {
1063 throw (IOException) new InterruptedIOException(
1064 "Interrupted while starting HTTP server").initCause(e);
1065 } catch (Exception e) {
1066 throw new IOException("Problem starting http server", e);
1067 }
1068 }
1069
1070 private void loadListeners() {
1071 for (ListenerInfo li : listeners) {
1072 webServer.addConnector(li.listener);
1073 }
1074 }
1075
1076
1077
1078
1079
1080 void openListeners() throws Exception {
1081 for (ListenerInfo li : listeners) {
1082 Connector listener = li.listener;
1083 if (!li.isManaged || li.listener.getLocalPort() != -1) {
1084
1085 continue;
1086 }
1087 int port = listener.getPort();
1088 while (true) {
1089
1090
1091 try {
1092 listener.close();
1093 listener.open();
1094 LOG.info("Jetty bound to port " + listener.getLocalPort());
1095 break;
1096 } catch (BindException ex) {
1097 if (port == 0 || !findPort) {
1098 BindException be = new BindException("Port in use: "
1099 + listener.getHost() + ":" + listener.getPort());
1100 be.initCause(ex);
1101 throw be;
1102 }
1103 }
1104
1105 listener.setPort(++port);
1106 Thread.sleep(100);
1107 }
1108 }
1109 }
1110
1111
1112
1113
1114 public void stop() throws Exception {
1115 MultiException exception = null;
1116 for (ListenerInfo li : listeners) {
1117 if (!li.isManaged) {
1118 continue;
1119 }
1120
1121 try {
1122 li.listener.close();
1123 } catch (Exception e) {
1124 LOG.error(
1125 "Error while stopping listener for webapp"
1126 + webAppContext.getDisplayName(), e);
1127 exception = addMultiException(exception, e);
1128 }
1129 }
1130
1131 try {
1132
1133 webAppContext.clearAttributes();
1134 webAppContext.stop();
1135 } catch (Exception e) {
1136 LOG.error("Error while stopping web app context for webapp "
1137 + webAppContext.getDisplayName(), e);
1138 exception = addMultiException(exception, e);
1139 }
1140
1141 try {
1142 webServer.stop();
1143 } catch (Exception e) {
1144 LOG.error("Error while stopping web server for webapp "
1145 + webAppContext.getDisplayName(), e);
1146 exception = addMultiException(exception, e);
1147 }
1148
1149 if (exception != null) {
1150 exception.ifExceptionThrow();
1151 }
1152
1153 }
1154
1155 private MultiException addMultiException(MultiException exception, Exception e) {
1156 if(exception == null){
1157 exception = new MultiException();
1158 }
1159 exception.add(e);
1160 return exception;
1161 }
1162
1163 public void join() throws InterruptedException {
1164 webServer.join();
1165 }
1166
1167
1168
1169
1170
1171 public boolean isAlive() {
1172 return webServer != null && webServer.isStarted();
1173 }
1174
1175
1176
1177
1178
1179 @Override
1180 public String toString() {
1181 if (listeners.size() == 0) {
1182 return "Inactive HttpServer";
1183 } else {
1184 StringBuilder sb = new StringBuilder("HttpServer (")
1185 .append(isAlive() ? STATE_DESCRIPTION_ALIVE : STATE_DESCRIPTION_NOT_LIVE).append("), listening at:");
1186 for (ListenerInfo li : listeners) {
1187 Connector l = li.listener;
1188 sb.append(l.getHost()).append(":").append(l.getPort()).append("/,");
1189 }
1190 return sb.toString();
1191 }
1192 }
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210 public static boolean isInstrumentationAccessAllowed(
1211 ServletContext servletContext, HttpServletRequest request,
1212 HttpServletResponse response) throws IOException {
1213 Configuration conf =
1214 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1215
1216 boolean access = true;
1217 boolean adminAccess = conf.getBoolean(
1218 CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN,
1219 false);
1220 if (adminAccess) {
1221 access = hasAdministratorAccess(servletContext, request, response);
1222 }
1223 return access;
1224 }
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236 public static boolean hasAdministratorAccess(
1237 ServletContext servletContext, HttpServletRequest request,
1238 HttpServletResponse response) throws IOException {
1239 Configuration conf =
1240 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1241
1242 if (!conf.getBoolean(
1243 CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
1244 return true;
1245 }
1246
1247 String remoteUser = request.getRemoteUser();
1248 if (remoteUser == null) {
1249 response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
1250 "Unauthenticated users are not " +
1251 "authorized to access this page.");
1252 return false;
1253 }
1254
1255 if (servletContext.getAttribute(ADMINS_ACL) != null &&
1256 !userHasAdministratorAccess(servletContext, remoteUser)) {
1257 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User "
1258 + remoteUser + " is unauthorized to access this page.");
1259 return false;
1260 }
1261
1262 return true;
1263 }
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274 public static boolean userHasAdministratorAccess(ServletContext servletContext,
1275 String remoteUser) {
1276 AccessControlList adminsAcl = (AccessControlList) servletContext
1277 .getAttribute(ADMINS_ACL);
1278 UserGroupInformation remoteUserUGI =
1279 UserGroupInformation.createRemoteUser(remoteUser);
1280 return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
1281 }
1282
1283
1284
1285
1286
1287
1288
1289 public static class StackServlet extends HttpServlet {
1290 private static final long serialVersionUID = -6284183679759467039L;
1291
1292 @Override
1293 public void doGet(HttpServletRequest request, HttpServletResponse response)
1294 throws ServletException, IOException {
1295 if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(),
1296 request, response)) {
1297 return;
1298 }
1299 response.setContentType("text/plain; charset=UTF-8");
1300 try (PrintStream out = new PrintStream(
1301 response.getOutputStream(), false, "UTF-8")) {
1302 Threads.printThreadInfo(out, "");
1303 out.flush();
1304 }
1305 ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
1306 }
1307 }
1308
1309
1310
1311
1312
1313
1314 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
1315 public static class QuotingInputFilter implements Filter {
1316 private FilterConfig config;
1317
1318 public static class RequestQuoter extends HttpServletRequestWrapper {
1319 private final HttpServletRequest rawRequest;
1320 public RequestQuoter(HttpServletRequest rawRequest) {
1321 super(rawRequest);
1322 this.rawRequest = rawRequest;
1323 }
1324
1325
1326
1327
1328 @SuppressWarnings("unchecked")
1329 @Override
1330 public Enumeration<String> getParameterNames() {
1331 return new Enumeration<String>() {
1332 private Enumeration<String> rawIterator =
1333 rawRequest.getParameterNames();
1334 @Override
1335 public boolean hasMoreElements() {
1336 return rawIterator.hasMoreElements();
1337 }
1338
1339 @Override
1340 public String nextElement() {
1341 return HtmlQuoting.quoteHtmlChars(rawIterator.nextElement());
1342 }
1343 };
1344 }
1345
1346
1347
1348
1349 @Override
1350 public String getParameter(String name) {
1351 return HtmlQuoting.quoteHtmlChars(rawRequest.getParameter
1352 (HtmlQuoting.unquoteHtmlChars(name)));
1353 }
1354
1355 @Override
1356 public String[] getParameterValues(String name) {
1357 String unquoteName = HtmlQuoting.unquoteHtmlChars(name);
1358 String[] unquoteValue = rawRequest.getParameterValues(unquoteName);
1359 if (unquoteValue == null) {
1360 return null;
1361 }
1362 String[] result = new String[unquoteValue.length];
1363 for(int i=0; i < result.length; ++i) {
1364 result[i] = HtmlQuoting.quoteHtmlChars(unquoteValue[i]);
1365 }
1366 return result;
1367 }
1368
1369 @SuppressWarnings("unchecked")
1370 @Override
1371 public Map<String, String[]> getParameterMap() {
1372 Map<String, String[]> result = new HashMap<String,String[]>();
1373 Map<String, String[]> raw = rawRequest.getParameterMap();
1374 for (Map.Entry<String,String[]> item: raw.entrySet()) {
1375 String[] rawValue = item.getValue();
1376 String[] cookedValue = new String[rawValue.length];
1377 for(int i=0; i< rawValue.length; ++i) {
1378 cookedValue[i] = HtmlQuoting.quoteHtmlChars(rawValue[i]);
1379 }
1380 result.put(HtmlQuoting.quoteHtmlChars(item.getKey()), cookedValue);
1381 }
1382 return result;
1383 }
1384
1385
1386
1387
1388
1389 @Override
1390 public StringBuffer getRequestURL(){
1391 String url = rawRequest.getRequestURL().toString();
1392 return new StringBuffer(HtmlQuoting.quoteHtmlChars(url));
1393 }
1394
1395
1396
1397
1398
1399 @Override
1400 public String getServerName() {
1401 return HtmlQuoting.quoteHtmlChars(rawRequest.getServerName());
1402 }
1403 }
1404
1405 @Override
1406 public void init(FilterConfig config) throws ServletException {
1407 this.config = config;
1408 }
1409
1410 @Override
1411 public void destroy() {
1412 }
1413
1414 @Override
1415 public void doFilter(ServletRequest request,
1416 ServletResponse response,
1417 FilterChain chain
1418 ) throws IOException, ServletException {
1419 HttpServletRequestWrapper quoted =
1420 new RequestQuoter((HttpServletRequest) request);
1421 HttpServletResponse httpResponse = (HttpServletResponse) response;
1422
1423 String mime = inferMimeType(request);
1424 if (mime == null) {
1425 httpResponse.setContentType("text/plain; charset=utf-8");
1426 } else if (mime.startsWith("text/html")) {
1427
1428
1429
1430
1431 httpResponse.setContentType("text/html; charset=utf-8");
1432 } else if (mime.startsWith("application/xml")) {
1433 httpResponse.setContentType("text/xml; charset=utf-8");
1434 }
1435 chain.doFilter(quoted, httpResponse);
1436 }
1437
1438
1439
1440
1441
1442 private String inferMimeType(ServletRequest request) {
1443 String path = ((HttpServletRequest)request).getRequestURI();
1444 ContextHandler.SContext sContext = (ContextHandler.SContext)config.getServletContext();
1445 MimeTypes mimes = sContext.getContextHandler().getMimeTypes();
1446 Buffer mimeBuffer = mimes.getMimeByExtension(path);
1447 return (mimeBuffer == null) ? null : mimeBuffer.toString();
1448 }
1449
1450 }
1451
1452 }