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