aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject aurora git commit: Adding H2 management console.
Date Thu, 04 Jun 2015 21:41:08 GMT
Repository: aurora
Updated Branches:
  refs/heads/master ed4415c12 -> a1f7b3da6


Adding H2 management console.

Bugs closed: AURORA-1287

Reviewed at https://reviews.apache.org/r/34566/


Project: http://git-wip-us.apache.org/repos/asf/aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/aurora/commit/a1f7b3da
Tree: http://git-wip-us.apache.org/repos/asf/aurora/tree/a1f7b3da
Diff: http://git-wip-us.apache.org/repos/asf/aurora/diff/a1f7b3da

Branch: refs/heads/master
Commit: a1f7b3da64c84fd84f145e0d4cce4ca69341f1ca
Parents: ed4415c
Author: Maxim Khutornenko <maxim@apache.org>
Authored: Thu Jun 4 14:40:47 2015 -0700
Committer: Maxim Khutornenko <maxim@apache.org>
Committed: Thu Jun 4 14:40:47 2015 -0700

----------------------------------------------------------------------
 .../aurora/scheduler/http/H2ConsoleModule.java  |  45 +++
 .../scheduler/http/JettyServerModule.java       |   5 +-
 .../http/api/security/ApiSecurityModule.java    | 189 -----------
 .../http/api/security/HttpSecurityModule.java   | 203 ++++++++++++
 .../http/api/security/ShiroIniParser.java       |   2 +-
 .../ShiroKerberosAuthenticationFilter.java      |  16 +-
 ...oKerberosPermissiveAuthenticationFilter.java |  51 +++
 .../scheduler/http/H2ConsoleModuleIT.java       |  40 +++
 .../http/api/security/ApiSecurityIT.java        | 274 ----------------
 .../http/api/security/HttpSecurityIT.java       | 324 +++++++++++++++++++
 .../ShiroAuthorizingParamInterceptorTest.java   |   4 +-
 .../ShiroKerberosAuthenticationFilterTest.java  |  25 +-
 ...berosPermissiveAuthenticationFilterTest.java |  98 ++++++
 .../aurora/e2e/test_kerberos_end_to_end.sh      |  20 ++
 14 files changed, 801 insertions(+), 495 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java b/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
new file mode 100644
index 0000000..a44ea08
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.http;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.servlet.ServletModule;
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+import org.h2.server.web.WebServlet;
+
+/**
+ * Binding module for the H2 management console.
+ * <p>
+ * See: http://www.h2database.com/html/tutorial.html#tutorial_starting_h2_console
+ */
+public class H2ConsoleModule extends ServletModule {
+  public static final String H2_PATH = "/h2console";
+  public static final String H2_PERM = "h2_management_console";
+
+  @CmdLine(name = "enable_h2_console", help = "Enable H2 DB management console.")
+  private static final Arg<Boolean> ENABLE_H2_CONSOLE = Arg.create(true);
+
+  @Override
+  protected void configureServlets() {
+    if (ENABLE_H2_CONSOLE.get()) {
+      filter(H2_PATH, H2_PATH + "/*").through(LeaderRedirectFilter.class);
+      serve(H2_PATH, H2_PATH + "/*").with(new WebServlet(), ImmutableMap.of(
+          "webAllowOthers", "true",
+          "ifExists", "true"
+      ));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
index 16515f6..b470129 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
@@ -64,7 +64,7 @@ import com.twitter.common.net.pool.DynamicHostSet.MonitorException;
 
 import org.apache.aurora.scheduler.SchedulerServicesModule;
 import org.apache.aurora.scheduler.http.api.ApiModule;
-import org.apache.aurora.scheduler.http.api.security.ApiSecurityModule;
+import org.apache.aurora.scheduler.http.api.security.HttpSecurityModule;
 import org.apache.aurora.scheduler.thrift.ThriftModule;
 import org.apache.aurora.scheduler.thrift.auth.ThriftAuthModule;
 import org.eclipse.jetty.rewrite.handler.RewriteHandler;
@@ -188,7 +188,8 @@ public class JettyServerModule extends AbstractModule {
           parentInjector,
           Modules.combine(
               new ApiModule(),
-              new ApiSecurityModule(),
+              new H2ConsoleModule(),
+              new HttpSecurityModule(),
               new ThriftModule(),
               new ThriftAuthModule()));
     }

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityModule.java b/src/main/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityModule.java
deleted file mode 100644
index 079ff5d..0000000
--- a/src/main/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityModule.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/**
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.http.api.security;
-
-import java.lang.reflect.Method;
-import java.util.Set;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.Key;
-import com.google.inject.Module;
-import com.google.inject.Provides;
-import com.google.inject.matcher.Matcher;
-import com.google.inject.matcher.Matchers;
-import com.google.inject.name.Names;
-import com.google.inject.servlet.RequestScoped;
-import com.google.inject.servlet.ServletModule;
-import com.twitter.common.args.Arg;
-import com.twitter.common.args.CmdLine;
-
-import org.aopalliance.intercept.MethodInterceptor;
-import org.apache.aurora.GuiceUtils;
-import org.apache.aurora.gen.AuroraAdmin;
-import org.apache.aurora.gen.AuroraSchedulerManager;
-import org.apache.aurora.scheduler.app.Modules;
-import org.apache.aurora.scheduler.http.api.ApiModule;
-import org.apache.aurora.scheduler.thrift.aop.AnnotatedAuroraAdmin;
-import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.guice.aop.ShiroAopModule;
-import org.apache.shiro.guice.web.ShiroWebModule;
-import org.apache.shiro.subject.Subject;
-import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
-
-import static java.util.Objects.requireNonNull;
-
-import static org.apache.aurora.scheduler.spi.Permissions.Domain.THRIFT_AURORA_ADMIN;
-import static org.apache.aurora.scheduler.spi.Permissions.Domain.THRIFT_AURORA_SCHEDULER_MANAGER;
-
-/**
- * Provides HTTP Basic Authentication for the API using Apache Shiro. When enabled, prevents
- * unauthenticated access to write APIs. Write API access must also be authorized, with permissions
- * configured in a shiro.ini file. For an example of this file, see the test resources included with
- * this package.
- */
-public class ApiSecurityModule extends ServletModule {
-  public static final String HTTP_REALM_NAME = "Apache Aurora Scheduler";
-
-  @CmdLine(name = "shiro_realm_modules",
-      help = "Guice modules for configuring Shiro Realms.")
-  private static final Arg<Set<Module>> SHIRO_REALM_MODULE = Arg.<Set<Module>>create(
-      ImmutableSet.of(Modules.lazilyInstantiated(IniShiroRealmModule.class)));
-
-  @VisibleForTesting
-  static final Matcher<Method> AURORA_SCHEDULER_MANAGER_SERVICE =
-      GuiceUtils.interfaceMatcher(AuroraSchedulerManager.Iface.class, true);
-
-  @VisibleForTesting
-  static final Matcher<Method> AURORA_ADMIN_SERVICE =
-      GuiceUtils.interfaceMatcher(AuroraAdmin.Iface.class, true);
-
-  public enum HttpAuthenticationMechanism {
-    /**
-     * No security.
-     */
-    NONE,
-
-    /**
-     * HTTP Basic Authentication, produces {@link org.apache.shiro.authc.UsernamePasswordToken}s.
-     */
-    BASIC,
-
-    /**
-     * Use GSS-Negotiate. Only Kerberos and SPNEGO-with-Kerberos GSS mechanisms are supported.
-     */
-    NEGOTIATE,
-  }
-
-  @CmdLine(name = "http_authentication_mechanism", help = "HTTP Authentication mechanism to use.")
-  private static final Arg<HttpAuthenticationMechanism> HTTP_AUTHENTICATION_MECHANISM =
-      Arg.create(HttpAuthenticationMechanism.NONE);
-
-  private final HttpAuthenticationMechanism mechanism;
-  private final Set<Module> shiroConfigurationModules;
-
-  public ApiSecurityModule() {
-    this(HTTP_AUTHENTICATION_MECHANISM.get(), SHIRO_REALM_MODULE.get());
-  }
-
-  @VisibleForTesting
-  ApiSecurityModule(Module shiroConfigurationModule) {
-    this(HttpAuthenticationMechanism.BASIC, ImmutableSet.of(shiroConfigurationModule));
-  }
-
-  private ApiSecurityModule(
-      HttpAuthenticationMechanism mechanism,
-      Set<Module> shiroConfigurationModules) {
-
-    this.mechanism = requireNonNull(mechanism);
-    this.shiroConfigurationModules = requireNonNull(shiroConfigurationModules);
-  }
-
-  @Override
-  protected void configureServlets() {
-    if (mechanism != HttpAuthenticationMechanism.NONE) {
-      doConfigureServlets();
-    }
-  }
-
-  private void doConfigureServlets() {
-    install(ShiroWebModule.guiceFilterModule(ApiModule.API_PATH));
-    install(new ShiroWebModule(getServletContext()) {
-      @Override
-      @SuppressWarnings("unchecked")
-      protected void configureShiroWeb() {
-        for (Module module : shiroConfigurationModules) {
-          // We can't wrap this in a PrivateModule because Guice Multibindings don't work with them
-          // and we need a Set<Realm>.
-          install(module);
-        }
-
-        switch (mechanism) {
-          case BASIC:
-            addFilterChain("/**",
-                ShiroWebModule.NO_SESSION_CREATION,
-                config(ShiroWebModule.AUTHC_BASIC, BasicHttpAuthenticationFilter.PERMISSIVE));
-            break;
-
-          case NEGOTIATE:
-            addFilterChain("/**",
-                ShiroWebModule.NO_SESSION_CREATION,
-                Key.get(ShiroKerberosAuthenticationFilter.class));
-            break;
-
-          default:
-            addError("Unrecognized HTTP authentication mechanism: " + mechanism);
-            break;
-        }
-      }
-    });
-
-    bindConstant().annotatedWith(Names.named("shiro.applicationName")).to(HTTP_REALM_NAME);
-
-    // TODO(ksweeney): Disable session cookie.
-    // TODO(ksweeney): Disable RememberMe cookie.
-
-    install(new ShiroAopModule());
-
-    // It is important that authentication happen before authorization is attempted, otherwise
-    // the authorizing interceptor will always fail.
-    MethodInterceptor authenticatingInterceptor = new ShiroAuthenticatingThriftInterceptor();
-    requestInjection(authenticatingInterceptor);
-    bindInterceptor(
-        Matchers.subclassesOf(AuroraSchedulerManager.Iface.class),
-        AURORA_SCHEDULER_MANAGER_SERVICE.or(AURORA_ADMIN_SERVICE),
-        authenticatingInterceptor);
-
-    MethodInterceptor apiInterceptor = new ShiroAuthorizingParamInterceptor(
-        THRIFT_AURORA_SCHEDULER_MANAGER);
-    requestInjection(apiInterceptor);
-    bindInterceptor(
-        Matchers.subclassesOf(AuroraSchedulerManager.Iface.class),
-        AURORA_SCHEDULER_MANAGER_SERVICE,
-        apiInterceptor);
-
-    MethodInterceptor adminInterceptor = new ShiroAuthorizingInterceptor(THRIFT_AURORA_ADMIN);
-    requestInjection(adminInterceptor);
-    bindInterceptor(
-        Matchers.subclassesOf(AnnotatedAuroraAdmin.class),
-        AURORA_ADMIN_SERVICE,
-        adminInterceptor);
-  }
-
-  @Provides
-  @RequestScoped
-  Subject provideSubject() {
-    return SecurityUtils.getSubject();
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityModule.java b/src/main/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityModule.java
new file mode 100644
index 0000000..1e9b1c3
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityModule.java
@@ -0,0 +1,203 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.http.api.security;
+
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import javax.servlet.Filter;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.name.Names;
+import com.google.inject.servlet.RequestScoped;
+import com.google.inject.servlet.ServletModule;
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.apache.aurora.GuiceUtils;
+import org.apache.aurora.gen.AuroraAdmin;
+import org.apache.aurora.gen.AuroraSchedulerManager;
+import org.apache.aurora.scheduler.app.Modules;
+import org.apache.aurora.scheduler.thrift.aop.AnnotatedAuroraAdmin;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.guice.aop.ShiroAopModule;
+import org.apache.shiro.guice.web.ShiroWebModule;
+import org.apache.shiro.subject.Subject;
+
+import static java.util.Objects.requireNonNull;
+
+import static org.apache.aurora.scheduler.http.H2ConsoleModule.H2_PATH;
+import static org.apache.aurora.scheduler.http.H2ConsoleModule.H2_PERM;
+import static org.apache.aurora.scheduler.http.api.ApiModule.API_PATH;
+import static org.apache.aurora.scheduler.spi.Permissions.Domain.THRIFT_AURORA_ADMIN;
+import static org.apache.aurora.scheduler.spi.Permissions.Domain.THRIFT_AURORA_SCHEDULER_MANAGER;
+import static org.apache.shiro.guice.web.ShiroWebModule.guiceFilterModule;
+import static org.apache.shiro.web.filter.authc.AuthenticatingFilter.PERMISSIVE;
+
+/**
+ * Provides HTTP Basic Authentication using Apache Shiro. When enabled, prevents unauthenticated
+ * access to write APIs and configured servlets. Write API access must also be authorized, with
+ * permissions configured in a shiro.ini file. For an example of this file, see the test resources
+ * included with this package.
+ */
+public class HttpSecurityModule extends ServletModule {
+  public static final String HTTP_REALM_NAME = "Apache Aurora Scheduler";
+
+  private static final String H2_PATTERN = H2_PATH + "/**";
+  private static final String ALL_PATTERN = "/**";
+  private static final Key<? extends Filter> K_STRICT =
+      Key.get(ShiroKerberosAuthenticationFilter.class);
+  private static final Key<? extends Filter> K_PERMISSIVE =
+      Key.get(ShiroKerberosPermissiveAuthenticationFilter.class);
+
+  @CmdLine(name = "shiro_realm_modules",
+      help = "Guice modules for configuring Shiro Realms.")
+  private static final Arg<Set<Module>> SHIRO_REALM_MODULE = Arg.<Set<Module>>create(
+      ImmutableSet.of(Modules.lazilyInstantiated(IniShiroRealmModule.class)));
+
+  @VisibleForTesting
+  static final Matcher<Method> AURORA_SCHEDULER_MANAGER_SERVICE =
+      GuiceUtils.interfaceMatcher(AuroraSchedulerManager.Iface.class, true);
+
+  @VisibleForTesting
+  static final Matcher<Method> AURORA_ADMIN_SERVICE =
+      GuiceUtils.interfaceMatcher(AuroraAdmin.Iface.class, true);
+
+  public enum HttpAuthenticationMechanism {
+    /**
+     * No security.
+     */
+    NONE,
+
+    /**
+     * HTTP Basic Authentication, produces {@link org.apache.shiro.authc.UsernamePasswordToken}s.
+     */
+    BASIC,
+
+    /**
+     * Use GSS-Negotiate. Only Kerberos and SPNEGO-with-Kerberos GSS mechanisms are supported.
+     */
+    NEGOTIATE,
+  }
+
+  @CmdLine(name = "http_authentication_mechanism", help = "HTTP Authentication mechanism to use.")
+  private static final Arg<HttpAuthenticationMechanism> HTTP_AUTHENTICATION_MECHANISM =
+      Arg.create(HttpAuthenticationMechanism.NONE);
+
+  private final HttpAuthenticationMechanism mechanism;
+  private final Set<Module> shiroConfigurationModules;
+
+  public HttpSecurityModule() {
+    this(HTTP_AUTHENTICATION_MECHANISM.get(), SHIRO_REALM_MODULE.get());
+  }
+
+  @VisibleForTesting
+  HttpSecurityModule(Module shiroConfigurationModule) {
+    this(HttpAuthenticationMechanism.BASIC, ImmutableSet.of(shiroConfigurationModule));
+  }
+
+  private HttpSecurityModule(
+      HttpAuthenticationMechanism mechanism,
+      Set<Module> shiroConfigurationModules) {
+
+    this.mechanism = requireNonNull(mechanism);
+    this.shiroConfigurationModules = requireNonNull(shiroConfigurationModules);
+  }
+
+  @Override
+  protected void configureServlets() {
+    if (mechanism != HttpAuthenticationMechanism.NONE) {
+      doConfigureServlets();
+    }
+  }
+
+  private void doConfigureServlets() {
+    install(guiceFilterModule(API_PATH));
+    install(guiceFilterModule(H2_PATH));
+    install(guiceFilterModule(H2_PATH + "/*"));
+    install(new ShiroWebModule(getServletContext()) {
+      @Override
+      @SuppressWarnings("unchecked")
+      protected void configureShiroWeb() {
+        for (Module module : shiroConfigurationModules) {
+          // We can't wrap this in a PrivateModule because Guice Multibindings don't work with them
+          // and we need a Set<Realm>.
+          install(module);
+        }
+
+        // Filter registration order is important here and is defined by the matching pattern:
+        // more specific pattern first.
+        switch (mechanism) {
+          case BASIC:
+            addFilterChain(H2_PATTERN, NO_SESSION_CREATION, AUTHC_BASIC, config(PERMS, H2_PERM));
+            addFilterChain(ALL_PATTERN, NO_SESSION_CREATION, config(AUTHC_BASIC, PERMISSIVE));
+            break;
+
+          case NEGOTIATE:
+            addFilterChain(H2_PATTERN, NO_SESSION_CREATION, K_STRICT, config(PERMS, H2_PERM));
+            addFilterChain(ALL_PATTERN, NO_SESSION_CREATION, K_PERMISSIVE);
+            break;
+
+          default:
+            addError("Unrecognized HTTP authentication mechanism: " + mechanism);
+            break;
+        }
+      }
+    });
+
+    bindConstant().annotatedWith(Names.named("shiro.applicationName")).to(HTTP_REALM_NAME);
+
+    // TODO(ksweeney): Disable session cookie.
+    // TODO(ksweeney): Disable RememberMe cookie.
+
+    install(new ShiroAopModule());
+
+    // It is important that authentication happen before authorization is attempted, otherwise
+    // the authorizing interceptor will always fail.
+    MethodInterceptor authenticatingInterceptor = new ShiroAuthenticatingThriftInterceptor();
+    requestInjection(authenticatingInterceptor);
+    bindInterceptor(
+        Matchers.subclassesOf(AuroraSchedulerManager.Iface.class),
+        AURORA_SCHEDULER_MANAGER_SERVICE.or(AURORA_ADMIN_SERVICE),
+        authenticatingInterceptor);
+
+    MethodInterceptor apiInterceptor = new ShiroAuthorizingParamInterceptor(
+        THRIFT_AURORA_SCHEDULER_MANAGER);
+    requestInjection(apiInterceptor);
+    bindInterceptor(
+        Matchers.subclassesOf(AuroraSchedulerManager.Iface.class),
+        AURORA_SCHEDULER_MANAGER_SERVICE,
+        apiInterceptor);
+
+    MethodInterceptor adminInterceptor = new ShiroAuthorizingInterceptor(THRIFT_AURORA_ADMIN);
+    requestInjection(adminInterceptor);
+    bindInterceptor(
+        Matchers.subclassesOf(AnnotatedAuroraAdmin.class),
+        AURORA_ADMIN_SERVICE,
+        adminInterceptor);
+  }
+
+  @Provides
+  @RequestScoped
+  Subject provideSubject() {
+    return SecurityUtils.getSubject();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroIniParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroIniParser.java b/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroIniParser.java
index 0163ba1..671b14f 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroIniParser.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroIniParser.java
@@ -30,7 +30,7 @@ import org.apache.shiro.realm.text.IniRealm;
  * The provided ini file must have only the sections required for configuration
  * ({@link IniRealm.ROLES_SECTION_NAME} and {@link IniRealm.USERS_SECTION_NAME}) and no extras -
  * Aurora uses Guice in to configure those sections in
- * {@link org.apache.aurora.scheduler.http.api.security.ApiSecurityModule}}.
+ * {@link HttpSecurityModule}}.
  */
 @ArgParser
 public class ShiroIniParser extends NonParameterizedTypeParser<Ini> {

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilter.java b/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilter.java
index 28e6b98..a18903e 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilter.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilter.java
@@ -28,7 +28,6 @@ import com.google.common.base.Optional;
 
 import org.apache.aurora.scheduler.http.AbstractFilter;
 import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authz.UnauthenticatedException;
 import org.apache.shiro.subject.Subject;
 
 import static java.util.Objects.requireNonNull;
@@ -75,15 +74,18 @@ public class ShiroKerberosAuthenticationFilter extends AbstractFilter {
         sendChallenge(response);
       }
     } else {
-      // Incoming request is unauthenticated, but some RPCs might be okay with that.
-      try {
-        chain.doFilter(request, response);
-      } catch (UnauthenticatedException e) {
-        sendChallenge(response);
-      }
+      handleUnauthenticated(request, response, chain);
     }
   }
 
+  protected void handleUnauthenticated(
+      HttpServletRequest request,
+      HttpServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+
+    sendChallenge(response);
+  }
+
   private void sendChallenge(HttpServletResponse response) throws IOException {
     response.setHeader(HttpHeaders.WWW_AUTHENTICATE, NEGOTIATE);
     response.sendError(HttpServletResponse.SC_UNAUTHORIZED);

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilter.java b/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilter.java
new file mode 100644
index 0000000..054d416
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilter.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.http.api.security;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * Allows unauthenticated requests to proceed. It's up to the decorated RPCs to enforce auth
+ * requirement by throwing {@link UnauthenticatedException}.
+ */
+public class ShiroKerberosPermissiveAuthenticationFilter extends ShiroKerberosAuthenticationFilter {
+  @Inject
+  ShiroKerberosPermissiveAuthenticationFilter(Provider<Subject> subjectProvider) {
+    super(subjectProvider);
+  }
+
+  @Override
+  protected void handleUnauthenticated(
+      HttpServletRequest request,
+      HttpServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+
+    // Incoming request is unauthenticated, but some RPCs might be okay with that.
+    try {
+      chain.doFilter(request, response);
+    } catch (UnauthenticatedException e) {
+      super.handleUnauthenticated(request, response, chain);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/java/org/apache/aurora/scheduler/http/H2ConsoleModuleIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/H2ConsoleModuleIT.java b/src/test/java/org/apache/aurora/scheduler/http/H2ConsoleModuleIT.java
new file mode 100644
index 0000000..9536fe3
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/http/H2ConsoleModuleIT.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.http;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import com.google.inject.Module;
+import com.sun.jersey.api.client.ClientResponse;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class H2ConsoleModuleIT extends JettyServerModuleTest {
+  @Override
+  protected Module getChildServletModule() {
+    return new H2ConsoleModule();
+  }
+
+  @Test
+  public void testConsoleBinding() {
+    replayAndStart();
+    ClientResponse response = getRequestBuilder(H2ConsoleModule.H2_PATH + "/")
+        .post(ClientResponse.class);
+    assertEquals(ClientResponse.Status.OK.getStatusCode(), response.getStatus());
+    assertEquals(MediaType.TEXT_HTML, response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityIT.java b/src/test/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityIT.java
deleted file mode 100644
index 6743d06..0000000
--- a/src/test/java/org/apache/aurora/scheduler/http/api/security/ApiSecurityIT.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/**
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.http.api.security;
-
-import java.io.IOException;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import com.google.common.testing.TearDown;
-import com.google.inject.AbstractModule;
-import com.google.inject.Module;
-import com.google.inject.util.Modules;
-import com.twitter.common.stats.StatsProvider;
-
-import org.apache.aurora.gen.AuroraAdmin;
-import org.apache.aurora.gen.Lock;
-import org.apache.aurora.gen.Response;
-import org.apache.aurora.gen.ResponseCode;
-import org.apache.aurora.gen.TaskQuery;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.base.Query;
-import org.apache.aurora.scheduler.http.JettyServerModuleTest;
-import org.apache.aurora.scheduler.http.api.ApiModule;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.apache.aurora.scheduler.thrift.aop.AnnotatedAuroraAdmin;
-import org.apache.aurora.scheduler.thrift.aop.MockDecoratedThrift;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.HttpClient;
-import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.shiro.config.Ini;
-import org.apache.shiro.realm.text.IniRealm;
-import org.apache.thrift.TException;
-import org.apache.thrift.protocol.TJSONProtocol;
-import org.apache.thrift.transport.THttpClient;
-import org.apache.thrift.transport.TTransport;
-import org.apache.thrift.transport.TTransportException;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.apache.aurora.scheduler.http.api.ApiModule.API_PATH;
-import static org.easymock.EasyMock.anyString;
-import static org.easymock.EasyMock.expect;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-public class ApiSecurityIT extends JettyServerModuleTest {
-  private static final Response OK = new Response().setResponseCode(ResponseCode.OK);
-
-  private static final UsernamePasswordCredentials ROOT =
-      new UsernamePasswordCredentials("root", "secret");
-  private static final UsernamePasswordCredentials WFARNER =
-      new UsernamePasswordCredentials("wfarner", "password");
-  private static final UsernamePasswordCredentials UNPRIVILEGED =
-      new UsernamePasswordCredentials("ksweeney", "12345");
-  private static final UsernamePasswordCredentials BACKUP_SERVICE =
-      new UsernamePasswordCredentials("backupsvc", "s3cret!!1");
-  private static final UsernamePasswordCredentials DEPLOY_SERVICE =
-      new UsernamePasswordCredentials("deploysvc", "0_0-x_0");
-
-  private static final UsernamePasswordCredentials INCORRECT =
-      new UsernamePasswordCredentials("root", "wrong");
-  private static final UsernamePasswordCredentials NONEXISTENT =
-      new UsernamePasswordCredentials("nobody", "12345");
-
-  private static final Set<Credentials> INVALID_CREDENTIALS =
-      ImmutableSet.<Credentials>of(INCORRECT, NONEXISTENT);
-
-  private static final Set<Credentials> VALID_CREDENTIALS =
-      ImmutableSet.<Credentials>of(ROOT, WFARNER, UNPRIVILEGED, BACKUP_SERVICE);
-
-  private static final IJobKey ADS_STAGING_JOB = JobKeys.from("ads", "staging", "job");
-
-  private Ini ini;
-  private AnnotatedAuroraAdmin auroraAdmin;
-  private StatsProvider statsProvider;
-
-  private static final Joiner COMMA_JOINER = Joiner.on(", ");
-  private static final String ADMIN_ROLE = "admin";
-  private static final String ENG_ROLE = "eng";
-  private static final String BACKUP_ROLE = "backup";
-  private static final String DEPLOY_ROLE = "deploy";
-
-  @Before
-  public void setUp() {
-    ini = new Ini();
-
-    Ini.Section users = ini.addSection(IniRealm.USERS_SECTION_NAME);
-    users.put(ROOT.getUserName(), COMMA_JOINER.join(ROOT.getPassword(), ADMIN_ROLE));
-    users.put(WFARNER.getUserName(), COMMA_JOINER.join(WFARNER.getPassword(), ENG_ROLE));
-    users.put(UNPRIVILEGED.getUserName(), UNPRIVILEGED.getPassword());
-    users.put(
-        BACKUP_SERVICE.getUserName(),
-        COMMA_JOINER.join(BACKUP_SERVICE.getPassword(), BACKUP_ROLE));
-    users.put(
-        DEPLOY_SERVICE.getUserName(),
-        COMMA_JOINER.join(DEPLOY_SERVICE.getPassword(), DEPLOY_ROLE));
-
-    Ini.Section roles = ini.addSection(IniRealm.ROLES_SECTION_NAME);
-    roles.put(ADMIN_ROLE, "*");
-    roles.put(ENG_ROLE, "thrift.AuroraSchedulerManager:*");
-    roles.put(BACKUP_ROLE, "thrift.AuroraAdmin:listBackups");
-    roles.put(
-        DEPLOY_ROLE,
-        "thrift.AuroraSchedulerManager:killTasks:"
-            + ADS_STAGING_JOB.getRole()
-            + ":"
-            + ADS_STAGING_JOB.getEnvironment()
-            + ":"
-            + ADS_STAGING_JOB.getName());
-
-    auroraAdmin = createMock(AnnotatedAuroraAdmin.class);
-    statsProvider = createMock(StatsProvider.class);
-    expect(statsProvider.makeCounter(anyString())).andStubReturn(new AtomicLong());
-  }
-
-  @Override
-  protected Module getChildServletModule() {
-    return Modules.combine(
-        new ApiModule(),
-        new ApiSecurityModule(new IniShiroRealmModule(ini)),
-        new AbstractModule() {
-          @Override
-          protected void configure() {
-            MockDecoratedThrift.bindForwardedMock(binder(), auroraAdmin);
-            bind(StatsProvider.class).toInstance(statsProvider);
-          }
-        });
-  }
-
-  private AuroraAdmin.Client getUnauthenticatedClient() throws TTransportException {
-    return getClient(null);
-  }
-
-  private AuroraAdmin.Client getClient(HttpClient httpClient) throws TTransportException {
-    final TTransport httpClientTransport = new THttpClient(
-        "http://" + httpServer.getHostText() + ":" + httpServer.getPort() + API_PATH,
-        httpClient);
-    addTearDown(new TearDown() {
-      @Override
-      public void tearDown() throws Exception {
-        httpClientTransport.close();
-      }
-    });
-    return new AuroraAdmin.Client(new TJSONProtocol(httpClientTransport));
-  }
-
-  private AuroraAdmin.Client getAuthenticatedClient(Credentials credentials)
-      throws TTransportException {
-
-    DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
-
-    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
-    credentialsProvider.setCredentials(AuthScope.ANY, credentials);
-    defaultHttpClient.setCredentialsProvider(credentialsProvider);
-
-    return getClient(defaultHttpClient);
-  }
-
-  @Test
-  public void testReadOnlyScheduler() throws TException {
-    expect(auroraAdmin.getRoleSummary()).andReturn(OK).times(3);
-
-    replayAndStart();
-
-    assertEquals(OK, getUnauthenticatedClient().getRoleSummary());
-    assertEquals(OK, getAuthenticatedClient(ROOT).getRoleSummary());
-    // Incorrect works because the server doesn't challenge for credentials to execute read-only
-    // methods.
-    assertEquals(OK, getAuthenticatedClient(INCORRECT).getRoleSummary());
-  }
-
-  private void assertKillTasksFails(AuroraAdmin.Client client) throws TException {
-    try {
-      client.killTasks(null, null, null);
-      fail("killTasks should fail.");
-    } catch (TTransportException e) {
-      // Expected.
-    }
-  }
-
-  @Test
-  public void testAuroraSchedulerManager() throws TException, IOException {
-    expect(auroraAdmin.killTasks(null, new Lock().setMessage("1"), null)).andReturn(OK);
-    expect(auroraAdmin.killTasks(null, new Lock().setMessage("2"), null)).andReturn(OK);
-
-    TaskQuery jobScopedQuery = Query.jobScoped(JobKeys.from("role", "env", "name")).get();
-    TaskQuery adsScopedQuery = Query.jobScoped(ADS_STAGING_JOB).get();
-    expect(auroraAdmin.killTasks(adsScopedQuery, null, null)).andReturn(OK);
-
-    replayAndStart();
-
-    assertEquals(OK,
-        getAuthenticatedClient(WFARNER).killTasks(null, new Lock().setMessage("1"), null));
-    assertEquals(OK,
-        getAuthenticatedClient(ROOT).killTasks(null, new Lock().setMessage("2"), null));
-    assertEquals(
-        ResponseCode.INVALID_REQUEST,
-        getAuthenticatedClient(UNPRIVILEGED).killTasks(null, null, null).getResponseCode());
-    assertEquals(
-        ResponseCode.AUTH_FAILED,
-        getAuthenticatedClient(UNPRIVILEGED)
-            .killTasks(jobScopedQuery, null, null)
-            .getResponseCode());
-    assertEquals(
-        ResponseCode.INVALID_REQUEST,
-        getAuthenticatedClient(BACKUP_SERVICE).killTasks(null, null, null).getResponseCode());
-    assertEquals(
-        ResponseCode.AUTH_FAILED,
-        getAuthenticatedClient(BACKUP_SERVICE)
-            .killTasks(jobScopedQuery, null, null)
-            .getResponseCode());
-    assertEquals(
-        ResponseCode.AUTH_FAILED,
-        getAuthenticatedClient(DEPLOY_SERVICE)
-            .killTasks(jobScopedQuery, null, null)
-            .getResponseCode());
-    assertEquals(
-        OK,
-        getAuthenticatedClient(DEPLOY_SERVICE).killTasks(adsScopedQuery, null, null));
-
-    assertKillTasksFails(getUnauthenticatedClient());
-    assertKillTasksFails(getAuthenticatedClient(INCORRECT));
-    assertKillTasksFails(getAuthenticatedClient(NONEXISTENT));
-  }
-
-  private void assertSnapshotFails(AuroraAdmin.Client client) throws TException {
-    try {
-      client.snapshot(null);
-      fail("snapshot should fail");
-    } catch (TTransportException e) {
-      // Expected.
-    }
-  }
-
-  @Test
-  public void testAuroraAdmin() throws TException {
-    expect(auroraAdmin.snapshot(null)).andReturn(OK);
-    expect(auroraAdmin.listBackups(null)).andReturn(OK);
-
-    replayAndStart();
-
-    assertEquals(OK, getAuthenticatedClient(ROOT).snapshot(null));
-
-    for (Credentials credentials : INVALID_CREDENTIALS) {
-      assertSnapshotFails(getAuthenticatedClient(credentials));
-    }
-
-    for (Credentials credentials : Sets.difference(VALID_CREDENTIALS, ImmutableSet.of(ROOT))) {
-      assertEquals(
-          ResponseCode.AUTH_FAILED,
-          getAuthenticatedClient(credentials).snapshot(null).getResponseCode());
-    }
-
-    assertEquals(OK, getAuthenticatedClient(BACKUP_SERVICE).listBackups(null));
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityIT.java b/src/test/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityIT.java
new file mode 100644
index 0000000..53ba949
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/http/api/security/HttpSecurityIT.java
@@ -0,0 +1,324 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.http.api.security;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.testing.TearDown;
+import com.google.inject.AbstractModule;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+import com.sun.jersey.api.client.ClientResponse;
+import com.twitter.common.stats.StatsProvider;
+
+import org.apache.aurora.gen.AuroraAdmin;
+import org.apache.aurora.gen.Lock;
+import org.apache.aurora.gen.Response;
+import org.apache.aurora.gen.ResponseCode;
+import org.apache.aurora.gen.TaskQuery;
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.base.Query;
+import org.apache.aurora.scheduler.http.H2ConsoleModule;
+import org.apache.aurora.scheduler.http.JettyServerModuleTest;
+import org.apache.aurora.scheduler.http.api.ApiModule;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.apache.aurora.scheduler.thrift.aop.AnnotatedAuroraAdmin;
+import org.apache.aurora.scheduler.thrift.aop.MockDecoratedThrift;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.thrift.TException;
+import org.apache.thrift.protocol.TJSONProtocol;
+import org.apache.thrift.transport.THttpClient;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.apache.aurora.scheduler.http.H2ConsoleModule.H2_PATH;
+import static org.apache.aurora.scheduler.http.H2ConsoleModule.H2_PERM;
+import static org.apache.aurora.scheduler.http.api.ApiModule.API_PATH;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class HttpSecurityIT extends JettyServerModuleTest {
+  private static final Response OK = new Response().setResponseCode(ResponseCode.OK);
+
+  private static final UsernamePasswordCredentials ROOT =
+      new UsernamePasswordCredentials("root", "secret");
+  private static final UsernamePasswordCredentials WFARNER =
+      new UsernamePasswordCredentials("wfarner", "password");
+  private static final UsernamePasswordCredentials UNPRIVILEGED =
+      new UsernamePasswordCredentials("ksweeney", "12345");
+  private static final UsernamePasswordCredentials BACKUP_SERVICE =
+      new UsernamePasswordCredentials("backupsvc", "s3cret!!1");
+  private static final UsernamePasswordCredentials DEPLOY_SERVICE =
+      new UsernamePasswordCredentials("deploysvc", "0_0-x_0");
+  private static final UsernamePasswordCredentials H2_USER =
+      new UsernamePasswordCredentials("dbuser", "pwd");
+
+  private static final UsernamePasswordCredentials INCORRECT =
+      new UsernamePasswordCredentials("root", "wrong");
+  private static final UsernamePasswordCredentials NONEXISTENT =
+      new UsernamePasswordCredentials("nobody", "12345");
+
+  private static final Set<Credentials> INVALID_CREDENTIALS =
+      ImmutableSet.<Credentials>of(INCORRECT, NONEXISTENT);
+
+  private static final Set<Credentials> VALID_CREDENTIALS =
+      ImmutableSet.<Credentials>of(ROOT, WFARNER, UNPRIVILEGED, BACKUP_SERVICE);
+
+  private static final IJobKey ADS_STAGING_JOB = JobKeys.from("ads", "staging", "job");
+
+  private Ini ini;
+  private AnnotatedAuroraAdmin auroraAdmin;
+  private StatsProvider statsProvider;
+
+  private static final Joiner COMMA_JOINER = Joiner.on(", ");
+  private static final String ADMIN_ROLE = "admin";
+  private static final String ENG_ROLE = "eng";
+  private static final String BACKUP_ROLE = "backup";
+  private static final String DEPLOY_ROLE = "deploy";
+  private static final String H2_ROLE = "h2access";
+
+  @Before
+  public void setUp() {
+    ini = new Ini();
+
+    Ini.Section users = ini.addSection(IniRealm.USERS_SECTION_NAME);
+    users.put(ROOT.getUserName(), COMMA_JOINER.join(ROOT.getPassword(), ADMIN_ROLE));
+    users.put(WFARNER.getUserName(), COMMA_JOINER.join(WFARNER.getPassword(), ENG_ROLE));
+    users.put(UNPRIVILEGED.getUserName(), UNPRIVILEGED.getPassword());
+    users.put(
+        BACKUP_SERVICE.getUserName(),
+        COMMA_JOINER.join(BACKUP_SERVICE.getPassword(), BACKUP_ROLE));
+    users.put(
+        DEPLOY_SERVICE.getUserName(),
+        COMMA_JOINER.join(DEPLOY_SERVICE.getPassword(), DEPLOY_ROLE));
+    users.put(H2_USER.getUserName(), COMMA_JOINER.join(H2_USER.getPassword(), H2_ROLE));
+
+    Ini.Section roles = ini.addSection(IniRealm.ROLES_SECTION_NAME);
+    roles.put(ADMIN_ROLE, "*");
+    roles.put(ENG_ROLE, "thrift.AuroraSchedulerManager:*");
+    roles.put(BACKUP_ROLE, "thrift.AuroraAdmin:listBackups");
+    roles.put(
+        DEPLOY_ROLE,
+        "thrift.AuroraSchedulerManager:killTasks:"
+            + ADS_STAGING_JOB.getRole()
+            + ":"
+            + ADS_STAGING_JOB.getEnvironment()
+            + ":"
+            + ADS_STAGING_JOB.getName());
+    roles.put(H2_ROLE, H2_PERM);
+
+    auroraAdmin = createMock(AnnotatedAuroraAdmin.class);
+    statsProvider = createMock(StatsProvider.class);
+    expect(statsProvider.makeCounter(anyString())).andStubReturn(new AtomicLong());
+  }
+
+  @Override
+  protected Module getChildServletModule() {
+    return Modules.combine(
+        new ApiModule(),
+        new H2ConsoleModule(),
+        new HttpSecurityModule(new IniShiroRealmModule(ini)),
+        new AbstractModule() {
+          @Override
+          protected void configure() {
+            MockDecoratedThrift.bindForwardedMock(binder(), auroraAdmin);
+            bind(StatsProvider.class).toInstance(statsProvider);
+          }
+        });
+  }
+
+  private AuroraAdmin.Client getUnauthenticatedClient() throws TTransportException {
+    return getClient(null);
+  }
+
+  private String formatUrl(String endpoint) {
+    return "http://" + httpServer.getHostText() + ":" + httpServer.getPort() + endpoint;
+  }
+
+  private AuroraAdmin.Client getClient(HttpClient httpClient) throws TTransportException {
+    final TTransport httpClientTransport = new THttpClient(formatUrl(API_PATH), httpClient);
+    addTearDown(new TearDown() {
+      @Override
+      public void tearDown() throws Exception {
+        httpClientTransport.close();
+      }
+    });
+    return new AuroraAdmin.Client(new TJSONProtocol(httpClientTransport));
+  }
+
+  private AuroraAdmin.Client getAuthenticatedClient(Credentials credentials)
+      throws TTransportException {
+
+    DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
+
+    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+    credentialsProvider.setCredentials(AuthScope.ANY, credentials);
+    defaultHttpClient.setCredentialsProvider(credentialsProvider);
+
+    return getClient(defaultHttpClient);
+  }
+
+  @Test
+  public void testReadOnlyScheduler() throws TException {
+    expect(auroraAdmin.getRoleSummary()).andReturn(OK).times(3);
+
+    replayAndStart();
+
+    assertEquals(OK, getUnauthenticatedClient().getRoleSummary());
+    assertEquals(OK, getAuthenticatedClient(ROOT).getRoleSummary());
+    // Incorrect works because the server doesn't challenge for credentials to execute read-only
+    // methods.
+    assertEquals(OK, getAuthenticatedClient(INCORRECT).getRoleSummary());
+  }
+
+  private void assertKillTasksFails(AuroraAdmin.Client client) throws TException {
+    try {
+      client.killTasks(null, null, null);
+      fail("killTasks should fail.");
+    } catch (TTransportException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testAuroraSchedulerManager() throws TException, IOException {
+    expect(auroraAdmin.killTasks(null, new Lock().setMessage("1"), null)).andReturn(OK);
+    expect(auroraAdmin.killTasks(null, new Lock().setMessage("2"), null)).andReturn(OK);
+
+    TaskQuery jobScopedQuery = Query.jobScoped(JobKeys.from("role", "env", "name")).get();
+    TaskQuery adsScopedQuery = Query.jobScoped(ADS_STAGING_JOB).get();
+    expect(auroraAdmin.killTasks(adsScopedQuery, null, null)).andReturn(OK);
+
+    replayAndStart();
+
+    assertEquals(OK,
+        getAuthenticatedClient(WFARNER).killTasks(null, new Lock().setMessage("1"), null));
+    assertEquals(OK,
+        getAuthenticatedClient(ROOT).killTasks(null, new Lock().setMessage("2"), null));
+    assertEquals(
+        ResponseCode.INVALID_REQUEST,
+        getAuthenticatedClient(UNPRIVILEGED).killTasks(null, null, null).getResponseCode());
+    assertEquals(
+        ResponseCode.AUTH_FAILED,
+        getAuthenticatedClient(UNPRIVILEGED)
+            .killTasks(jobScopedQuery, null, null)
+            .getResponseCode());
+    assertEquals(
+        ResponseCode.INVALID_REQUEST,
+        getAuthenticatedClient(BACKUP_SERVICE).killTasks(null, null, null).getResponseCode());
+    assertEquals(
+        ResponseCode.AUTH_FAILED,
+        getAuthenticatedClient(BACKUP_SERVICE)
+            .killTasks(jobScopedQuery, null, null)
+            .getResponseCode());
+    assertEquals(
+        ResponseCode.AUTH_FAILED,
+        getAuthenticatedClient(DEPLOY_SERVICE)
+            .killTasks(jobScopedQuery, null, null)
+            .getResponseCode());
+    assertEquals(
+        OK,
+        getAuthenticatedClient(DEPLOY_SERVICE).killTasks(adsScopedQuery, null, null));
+
+    assertKillTasksFails(getUnauthenticatedClient());
+    assertKillTasksFails(getAuthenticatedClient(INCORRECT));
+    assertKillTasksFails(getAuthenticatedClient(NONEXISTENT));
+  }
+
+  private void assertSnapshotFails(AuroraAdmin.Client client) throws TException {
+    try {
+      client.snapshot(null);
+      fail("snapshot should fail");
+    } catch (TTransportException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testAuroraAdmin() throws TException {
+    expect(auroraAdmin.snapshot(null)).andReturn(OK);
+    expect(auroraAdmin.listBackups(null)).andReturn(OK);
+
+    replayAndStart();
+
+    assertEquals(OK, getAuthenticatedClient(ROOT).snapshot(null));
+
+    for (Credentials credentials : INVALID_CREDENTIALS) {
+      assertSnapshotFails(getAuthenticatedClient(credentials));
+    }
+
+    for (Credentials credentials : Sets.difference(VALID_CREDENTIALS, ImmutableSet.of(ROOT))) {
+      assertEquals(
+          ResponseCode.AUTH_FAILED,
+          getAuthenticatedClient(credentials).snapshot(null).getResponseCode());
+    }
+
+    assertEquals(OK, getAuthenticatedClient(BACKUP_SERVICE).listBackups(null));
+  }
+
+  private HttpResponse callH2Console(Credentials credentials) throws Exception {
+    DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
+
+    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+    credentialsProvider.setCredentials(AuthScope.ANY, credentials);
+    defaultHttpClient.setCredentialsProvider(credentialsProvider);
+    return defaultHttpClient.execute(new HttpPost(formatUrl(H2_PATH + "/")));
+  }
+
+  @Test
+  public void testH2ConsoleUser() throws Exception {
+    replayAndStart();
+
+    assertEquals(
+        ClientResponse.Status.OK.getStatusCode(),
+        callH2Console(H2_USER).getStatusLine().getStatusCode());
+  }
+
+  @Test
+  public void testH2ConsoleAdmin() throws Exception {
+    replayAndStart();
+
+    assertEquals(
+        ClientResponse.Status.OK.getStatusCode(),
+        callH2Console(ROOT).getStatusLine().getStatusCode());
+  }
+
+  @Test
+  public void testH2ConsoleUnauthorized() throws Exception {
+    replayAndStart();
+
+    assertEquals(
+        ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
+        callH2Console(UNPRIVILEGED).getStatusLine().getStatusCode());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroAuthorizingParamInterceptorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroAuthorizingParamInterceptorTest.java b/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroAuthorizingParamInterceptorTest.java
index 33783c8..b2b74c6 100644
--- a/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroAuthorizingParamInterceptorTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroAuthorizingParamInterceptorTest.java
@@ -82,7 +82,7 @@ public class ShiroAuthorizingParamInterceptorTest extends EasyMockTest {
             MockDecoratedThrift.bindForwardedMock(binder(), thrift);
             bindInterceptor(
                 Matchers.subclassesOf(AnnotatedAuroraAdmin.class),
-                ApiSecurityModule.AURORA_SCHEDULER_MANAGER_SERVICE,
+                HttpSecurityModule.AURORA_SCHEDULER_MANAGER_SERVICE,
                 interceptor);
             bind(StatsProvider.class).toInstance(statsProvider);
             requestInjection(interceptor);
@@ -95,7 +95,7 @@ public class ShiroAuthorizingParamInterceptorTest extends EasyMockTest {
     control.replay();
 
     for (Method method : AnnotatedAuroraAdmin.class.getMethods()) {
-      if (ApiSecurityModule.AURORA_SCHEDULER_MANAGER_SERVICE.matches(method)) {
+      if (HttpSecurityModule.AURORA_SCHEDULER_MANAGER_SERVICE.matches(method)) {
         interceptor.getAuthorizingParamGetters().getUnchecked(method);
       }
     }

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilterTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilterTest.java b/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilterTest.java
index e335a43..f35dcb8 100644
--- a/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilterTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosAuthenticationFilterTest.java
@@ -29,7 +29,6 @@ import com.sun.jersey.api.client.ClientResponse;
 import org.apache.aurora.scheduler.http.JettyServerModuleTest;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.authz.UnauthenticatedException;
 import org.apache.shiro.subject.Subject;
 import org.junit.Before;
 import org.junit.Test;
@@ -79,13 +78,14 @@ public class ShiroKerberosAuthenticationFilterTest extends JettyServerModuleTest
   }
 
   @Test
-  public void testPermitsUnauthenticated() throws ServletException, IOException {
-    mockServlet.service(anyObject(HttpServletRequest.class), anyObject(HttpServletResponse.class));
-
+  public void testDoesNotPermitUnauthenticated() throws ServletException, IOException {
     replayAndStart();
 
     ClientResponse clientResponse = getRequestBuilder(PATH).get(ClientResponse.class);
-    assertEquals(HttpServletResponse.SC_OK, clientResponse.getStatus());
+    assertEquals(HttpServletResponse.SC_UNAUTHORIZED, clientResponse.getStatus());
+    assertEquals(
+        ShiroKerberosAuthenticationFilter.NEGOTIATE,
+        clientResponse.getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE));
   }
 
   @Test
@@ -130,19 +130,4 @@ public class ShiroKerberosAuthenticationFilterTest extends JettyServerModuleTest
 
     assertEquals(HttpServletResponse.SC_OK, clientResponse.getStatus());
   }
-
-  @Test
-  public void testInterceptsUnauthenticatedException() throws ServletException, IOException {
-    mockServlet.service(anyObject(HttpServletRequest.class), anyObject(HttpServletResponse.class));
-    expectLastCall().andThrow(new UnauthenticatedException());
-
-    replayAndStart();
-
-    ClientResponse clientResponse = getRequestBuilder(PATH).get(ClientResponse.class);
-
-    assertEquals(HttpServletResponse.SC_UNAUTHORIZED, clientResponse.getStatus());
-    assertEquals(
-        ShiroKerberosAuthenticationFilter.NEGOTIATE,
-        clientResponse.getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE));
-  }
 }

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilterTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilterTest.java b/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilterTest.java
new file mode 100644
index 0000000..6eb82b5
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/http/api/security/ShiroKerberosPermissiveAuthenticationFilterTest.java
@@ -0,0 +1,98 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.http.api.security;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+
+import com.google.inject.Module;
+import com.google.inject.servlet.ServletModule;
+import com.google.inject.util.Providers;
+import com.sun.jersey.api.client.ClientResponse;
+
+import org.apache.aurora.scheduler.http.JettyServerModuleTest;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertEquals;
+
+public class ShiroKerberosPermissiveAuthenticationFilterTest extends JettyServerModuleTest {
+  private static final String PATH = "/test";
+
+  private Subject subject;
+  private HttpServlet mockServlet;
+
+  private ShiroKerberosPermissiveAuthenticationFilter filter;
+
+  @Before
+  public void setUp() {
+    subject = createMock(Subject.class);
+    mockServlet = createMock(HttpServlet.class);
+
+    filter = new ShiroKerberosPermissiveAuthenticationFilter(Providers.of(subject));
+  }
+
+  @Override
+  public Module getChildServletModule() {
+    return new ServletModule() {
+      @Override
+      protected void configureServlets() {
+        filter(PATH).through(filter);
+        serve(PATH).with(new HttpServlet() {
+          @Override
+          protected void service(HttpServletRequest req, HttpServletResponse resp)
+              throws ServletException, IOException {
+
+            mockServlet.service(req, resp);
+            resp.setStatus(HttpServletResponse.SC_OK);
+          }
+        });
+      }
+    };
+  }
+
+  @Test
+  public void testPermitsUnauthenticated() throws ServletException, IOException {
+    mockServlet.service(anyObject(HttpServletRequest.class), anyObject(HttpServletResponse.class));
+
+    replayAndStart();
+
+    ClientResponse clientResponse = getRequestBuilder(PATH).get(ClientResponse.class);
+    assertEquals(HttpServletResponse.SC_OK, clientResponse.getStatus());
+  }
+
+  @Test
+  public void testInterceptsUnauthenticatedException() throws ServletException, IOException {
+    mockServlet.service(anyObject(HttpServletRequest.class), anyObject(HttpServletResponse.class));
+    expectLastCall().andThrow(new UnauthenticatedException());
+
+    replayAndStart();
+
+    ClientResponse clientResponse = getRequestBuilder(PATH).get(ClientResponse.class);
+
+    assertEquals(HttpServletResponse.SC_UNAUTHORIZED, clientResponse.getStatus());
+    assertEquals(
+        ShiroKerberosAuthenticationFilter.NEGOTIATE,
+        clientResponse.getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/a1f7b3da/src/test/sh/org/apache/aurora/e2e/test_kerberos_end_to_end.sh
----------------------------------------------------------------------
diff --git a/src/test/sh/org/apache/aurora/e2e/test_kerberos_end_to_end.sh b/src/test/sh/org/apache/aurora/e2e/test_kerberos_end_to_end.sh
index 6e9e3b2..4d6043a 100755
--- a/src/test/sh/org/apache/aurora/e2e/test_kerberos_end_to_end.sh
+++ b/src/test/sh/org/apache/aurora/e2e/test_kerberos_end_to_end.sh
@@ -61,6 +61,16 @@ function snapshot_as {
   kdestroy
 }
 
+readonly H2_RESPONSE_OUTFILE="h2console-response.%s.json"
+function h2console_as {
+  local principal=$1
+  kinit -k -t "testdir/${principal}.keytab" $principal
+  curl -u : --negotiate -w '%{http_code}\n' \
+    -o $(printf $H2_RESPONSE_OUTFILE $principal) \
+    -s 'http://192.168.33.7:8081/h2console/'
+  kdestroy
+}
+
 function setup {
   cat >> $KRB5_CONFIG <<EOF
 [domain_realm]
@@ -103,6 +113,15 @@ function test_snapshot {
   grep -qv 'lacks permission' snapshot-response.root.json
 }
 
+function test_h2console {
+  h2console_as vagrant
+  grep -q 'Error 401 Unauthorized' h2console-response.vagrant.json
+  h2console_as unpriv
+  grep -q 'Error 401 Unauthorized' h2console-response.unpriv.json
+  h2console_as root
+  grep -q 'Welcome to H2' h2console-response.root.json
+}
+
 function test_clients {
   sudo cp /vagrant/examples/vagrant/clusters_kerberos.json /etc/aurora/clusters.json
 
@@ -129,6 +148,7 @@ function main {
     setup
     test_snapshot
     test_clients
+    test_h2console
     set +x
     echo
     echo '*** OK (All tests passed) ***'


Mime
View raw message