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 <[email protected]> Authored: Thu Jun 4 14:40:47 2015 -0700 Committer: Maxim Khutornenko <[email protected]> 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) ***'
