diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-04-06 10:28:30 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-04-09 12:49:33 +0200 |
commit | 457aad058787375f6f17fb99b263747aeddec59f (patch) | |
tree | dad84f845411ae798f4f7e158f494792ede2934a /controller-server/src | |
parent | 773bb8bf0f99fe1a2a7cec5aa026f0bbf43c7f7d (diff) |
Use CorsSecurityRequestFilterBase for controller filters
Diffstat (limited to 'controller-server/src')
8 files changed, 104 insertions, 111 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java index 5ad44b82370..767a1da3eb0 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java @@ -3,9 +3,9 @@ package com.yahoo.vespa.hosted.controller.athenz.filter; import com.google.inject.Inject; import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; -import com.yahoo.jdisc.http.filter.SecurityRequestFilter; +import com.yahoo.jdisc.http.filters.cors.CorsSecurityFilterConfig; +import com.yahoo.jdisc.http.filters.cors.CorsSecurityRequestFilterBase; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.utils.AthenzIdentities; @@ -13,11 +13,12 @@ import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import java.security.cert.X509Certificate; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Executor; -import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUtils.sendErrorResponse; /** * Authenticates Athenz principal, either through: @@ -30,7 +31,7 @@ import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUti * @author bjorncs */ // TODO bjorncs: Move this class to vespa-athenz bundle -public class AthenzPrincipalFilter implements SecurityRequestFilter { +public class AthenzPrincipalFilter extends CorsSecurityRequestFilterBase { private final NTokenValidator validator; private final String principalTokenHeader; @@ -39,18 +40,25 @@ public class AthenzPrincipalFilter implements SecurityRequestFilter { * @param executor to preload the ZMS public keys with */ @Inject - public AthenzPrincipalFilter(ZmsKeystore zmsKeystore, Executor executor, AthenzConfig config) { - this(new NTokenValidator(zmsKeystore), executor, config.principalHeaderName()); + public AthenzPrincipalFilter(ZmsKeystore zmsKeystore, + Executor executor, + AthenzConfig athenzConfig, + CorsSecurityFilterConfig corsConfig) { + this(new NTokenValidator(zmsKeystore), executor, athenzConfig.principalHeaderName(), new HashSet<>(corsConfig.allowedUrls())); } - AthenzPrincipalFilter(NTokenValidator validator, Executor executor, String principalTokenHeader) { + AthenzPrincipalFilter(NTokenValidator validator, + Executor executor, + String principalTokenHeader, + Set<String> corsAllowedUrls) { + super(corsAllowedUrls); this.validator = validator; this.principalTokenHeader = principalTokenHeader; executor.execute(validator::preloadPublicKeys); } @Override - public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { + public Optional<ErrorResponse> filter(DiscFilterRequest request) { try { Optional<AthenzPrincipal> certificatePrincipal = getClientCertificate(request) .map(AthenzIdentities::from) @@ -61,8 +69,7 @@ public class AthenzPrincipalFilter implements SecurityRequestFilter { if (!certificatePrincipal.isPresent() && !nTokenPrincipal.isPresent()) { String errorMessage = "Unable to authenticate Athenz identity. " + "Either client certificate or principal token is required."; - sendErrorResponse(responseHandler, Response.Status.UNAUTHORIZED, errorMessage); - return; + return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, errorMessage)); } if (certificatePrincipal.isPresent() && nTokenPrincipal.isPresent() && !certificatePrincipal.get().getIdentity().equals(nTokenPrincipal.get().getIdentity())) { @@ -70,14 +77,14 @@ public class AthenzPrincipalFilter implements SecurityRequestFilter { "Identity in principal token does not match x509 CN: token-identity=%s, cert-identity=%s", nTokenPrincipal.get().getIdentity().getFullName(), certificatePrincipal.get().getIdentity().getFullName()); - sendErrorResponse(responseHandler, Response.Status.UNAUTHORIZED, errorMessage); - return; + return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, errorMessage)); } AthenzPrincipal principal = nTokenPrincipal.orElseGet(certificatePrincipal::get); request.setUserPrincipal(principal); request.setRemoteUser(principal.getName()); + return Optional.empty(); } catch (Exception e) { - sendErrorResponse(responseHandler,Response.Status.UNAUTHORIZED, e.getMessage()); + return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, e.getMessage())); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java index 1208eac74df..27e3b339b38 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java @@ -3,8 +3,8 @@ package com.yahoo.vespa.hosted.controller.athenz.filter; import com.google.inject.Inject; import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; +import com.yahoo.jdisc.http.filters.cors.CorsSecurityFilterConfig; import com.yahoo.log.LogLevel; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.AthenzUser; @@ -20,7 +20,6 @@ import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Stream; -import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUtils.sendErrorResponse; /** * A variant of the {@link AthenzPrincipalFilter} to be used in combination with a cookie-based @@ -30,7 +29,7 @@ import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUti * @author bjorncs */ // TODO Remove this filter once migrated to Okta -@After({"AccessControlRequestFilter", "BouncerFilter"}) +@After({"CorsPreflightSecurityRequestFilter", "BouncerFilter"}) public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter { private static final Logger log = Logger.getLogger(UserAuthWithAthenzPrincipalFilter.class.getName()); @@ -39,34 +38,35 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter { private final String principalHeaderName; @Inject - public UserAuthWithAthenzPrincipalFilter(ZmsKeystore zmsKeystore, Executor executor, AthenzConfig config) { - super(zmsKeystore, executor, config); - this.userAuthenticationPassThruAttribute = config.userAuthenticationPassThruAttribute(); - this.principalHeaderName = config.principalHeaderName(); + public UserAuthWithAthenzPrincipalFilter(ZmsKeystore zmsKeystore, + Executor executor, + AthenzConfig athenzConfig, + CorsSecurityFilterConfig corsConfig) { + super(zmsKeystore, executor, athenzConfig, corsConfig); + this.userAuthenticationPassThruAttribute = athenzConfig.userAuthenticationPassThruAttribute(); + this.principalHeaderName = athenzConfig.principalHeaderName(); } @Override - public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { - if (request.getMethod().equals("OPTIONS")) return; // Skip authentication on OPTIONS - required for Javascript CORS + public Optional<ErrorResponse> filter(DiscFilterRequest request) { + if (request.getMethod().equals("OPTIONS")) return Optional.empty(); // Skip authentication on OPTIONS - required for Javascript CORS try { switch (getUserAuthenticationResult(request)) { case USER_COOKIE_MISSING: case USER_COOKIE_ALTERNATIVE_MISSING: - super.filter(request, responseHandler); // Cookie-based authentication failed, delegate to Athenz - break; + return super.filter(request); // Cookie-based authentication failed, delegate to Athenz case USER_COOKIE_OK: rewriteUserPrincipalToAthenz(request); - return; // Authenticated using user cookie + return Optional.empty(); // Authenticated using user cookie case USER_COOKIE_INVALID: - sendErrorResponse(responseHandler, - Response.Status.UNAUTHORIZED, - "Your user cookie is invalid (either expired, tampered or invalid ip)"); - break; + return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Your user cookie is invalid (either expired, tampered or invalid ip)")); + default: + return Optional.empty(); } } catch (Exception e) { log.log(LogLevel.WARNING, "Authentication failed: " + e.getMessage(), e); - sendErrorResponse(responseHandler, Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()); + return Optional.of(new ErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage())); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java index dc9ead96e0a..b7f4d8e490a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java @@ -4,11 +4,10 @@ package com.yahoo.vespa.hosted.controller.restapi.filter; import com.google.inject.Inject; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.TenantName; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.jdisc.http.filter.DiscFilterRequest; -import com.yahoo.jdisc.http.filter.SecurityRequestFilter; +import com.yahoo.jdisc.http.filters.cors.CorsSecurityFilterConfig; +import com.yahoo.jdisc.http.filters.cors.CorsSecurityRequestFilterBase; import com.yahoo.log.LogLevel; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; @@ -33,6 +32,7 @@ import javax.ws.rs.WebApplicationException; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.logging.Logger; import static com.yahoo.jdisc.http.HttpRequest.Method.GET; @@ -41,7 +41,6 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.OPTIONS; import static com.yahoo.jdisc.http.HttpRequest.Method.POST; import static com.yahoo.jdisc.http.HttpRequest.Method.PUT; import static com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities.SCREWDRIVER_DOMAIN; -import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUtils.sendErrorResponse; /** * A security filter protects all controller apis. @@ -50,7 +49,7 @@ import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUti */ @After("com.yahoo.vespa.hosted.controller.athenz.filter.UserAuthWithAthenzPrincipalFilter") @Provides("ControllerAuthorizationFilter") -public class ControllerAuthorizationFilter implements SecurityRequestFilter { +public class ControllerAuthorizationFilter extends CorsSecurityRequestFilterBase { private static final List<Method> WHITELISTED_METHODS = Arrays.asList(GET, OPTIONS, HEAD); @@ -58,31 +57,30 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { private final AthenzClientFactory clientFactory; private final TenantController tenantController; - private final AuthorizationResponseHandler authorizationResponseHandler; - - public interface AuthorizationResponseHandler { - void handle(ResponseHandler responseHandler, DiscFilterRequest request, WebApplicationException verificationException); - } @Inject - public ControllerAuthorizationFilter(AthenzClientFactory clientFactory, Controller controller) { - this(clientFactory, controller.tenants(), new DefaultAuthorizationResponseHandler()); + public ControllerAuthorizationFilter(AthenzClientFactory clientFactory, + Controller controller, + CorsSecurityFilterConfig corsConfig) { + super(corsConfig); + this.clientFactory = clientFactory; + this.tenantController = controller.tenants(); } ControllerAuthorizationFilter(AthenzClientFactory clientFactory, TenantController tenantController, - AuthorizationResponseHandler authorizationResponseHandler) { + Set<String> allowedUrls) { + super(allowedUrls); this.clientFactory = clientFactory; this.tenantController = tenantController; - this.authorizationResponseHandler = authorizationResponseHandler; } // NOTE: Be aware of the ordering of the path pattern matching. Semantics may change if the patterns are evaluated // in different order. @Override - public void filter(DiscFilterRequest request, ResponseHandler handler) { + public Optional<ErrorResponse> filter(DiscFilterRequest request) { Method method = getMethod(request); - if (isWhiteListedMethod(method)) return; + if (isWhiteListedMethod(method)) return Optional.empty(); try { Path path = new Path(request.getRequestURI()); @@ -98,8 +96,12 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { } else { throw new ForbiddenException("No access control is explicitly declared for this api."); } + return Optional.empty(); } catch (WebApplicationException e) { - authorizationResponseHandler.handle(handler, request, e); + int statusCode = e.getResponse().getStatus(); + String errorMessage = e.getMessage(); + log.log(LogLevel.WARNING, String.format("Access denied(%d): %s", statusCode, errorMessage), e); + return Optional.of(new ErrorResponse(statusCode, errorMessage)); } } @@ -242,20 +244,4 @@ public class ControllerAuthorizationFilter implements SecurityRequestFilter { .map(AthenzPrincipal.class::cast); } - /** - * Maps {@link WebApplicationException} to http response ({@link Response}. - */ - private static class DefaultAuthorizationResponseHandler implements AuthorizationResponseHandler { - @Override - public void handle(ResponseHandler responseHandler, - DiscFilterRequest request, - WebApplicationException exception) { - int statusCode = exception.getResponse().getStatus(); - String errorMessage = exception.getMessage(); - log.log(LogLevel.WARNING, String.format("Access denied(%d): %s", statusCode, errorMessage), exception); - sendErrorResponse(responseHandler, statusCode, errorMessage); - } - } - - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SecurityFilterUtils.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SecurityFilterUtils.java deleted file mode 100644 index 7e7cf7a575d..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SecurityFilterUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.restapi.filter; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.FastContentWriter; -import com.yahoo.jdisc.handler.ResponseDispatch; -import com.yahoo.jdisc.handler.ResponseHandler; - -/** - * @author bjorncs - */ -public class SecurityFilterUtils { - private static final ObjectMapper mapper = new ObjectMapper(); - - private SecurityFilterUtils() {} - - public static void sendErrorResponse(ResponseHandler responseHandler, int statusCode, String message) { - Response response = new Response(statusCode); - response.headers().put("Content-Type", "application/json"); - ObjectNode errorMessage = mapper.createObjectNode(); - errorMessage.put("message", message); - try (FastContentWriter writer = ResponseDispatch.newInstance(response).connectFastWriter(responseHandler)) { - writer.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errorMessage)); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java index 0a32406be60..ab97aaae201 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java @@ -1,6 +1,12 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.handler.FastContentWriter; +import com.yahoo.jdisc.handler.ResponseDispatch; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.HttpResponse; import com.yahoo.jdisc.http.filter.DiscFilterRequest; @@ -11,8 +17,6 @@ import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.utils.AthenzIdentities; import com.yahoo.yolean.chain.Before; -import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUtils.sendErrorResponse; - /** * @author bjorncs */ @@ -22,13 +26,23 @@ public class AthenzFilterMock implements SecurityRequestFilter { public static final String IDENTITY_HEADER_NAME = "Athenz-Identity"; public static final String ATHENZ_NTOKEN_HEADER_NAME = "Athenz-NToken"; + private static final ObjectMapper mapper = new ObjectMapper(); + @Override public void filter(DiscFilterRequest request, ResponseHandler handler) { if (request.getMethod().equalsIgnoreCase("OPTIONS")) return; String identityName = request.getHeader(IDENTITY_HEADER_NAME); String nToken = request.getHeader(ATHENZ_NTOKEN_HEADER_NAME); if (identityName == null) { - sendErrorResponse(handler, HttpResponse.Status.UNAUTHORIZED, "Not authenticated"); + Response response = new Response(HttpResponse.Status.UNAUTHORIZED); + response.headers().put("Content-Type", "application/json"); + ObjectNode errorMessage = mapper.createObjectNode(); + errorMessage.put("message", "Not authenticated"); + try (FastContentWriter writer = ResponseDispatch.newInstance(response).connectFastWriter(handler)) { + writer.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errorMessage)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } } else { AthenzIdentity identity = AthenzIdentities.from(identityName); AthenzPrincipal principal = diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java index 53ced43a9ba..f1a85496bdb 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java @@ -34,11 +34,14 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; +import java.util.Collections; import java.util.Date; import java.util.Objects; +import java.util.Set; import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED; import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.joining; import static org.hamcrest.Matchers.containsString; @@ -58,6 +61,8 @@ public class AthenzPrincipalFilterTest { private static final String ATHENZ_PRINCIPAL_HEADER = "Athenz-Principal-Auth"; private static final AthenzIdentity IDENTITY = AthenzUser.fromUserId("bob"); private static final X509Certificate CERTIFICATE = createSelfSignedCertificate(IDENTITY); + private static final String ORIGIN = "http://localhost"; + private static final Set<String> CORS_ALLOWED_URLS = singleton(ORIGIN); private NTokenValidator validator; @@ -68,27 +73,33 @@ public class AthenzPrincipalFilterTest { @Test public void valid_ntoken_is_accepted() { - DiscFilterRequest request = mock(DiscFilterRequest.class); + DiscFilterRequest request = createRequestMock(); AthenzPrincipal principal = new AthenzPrincipal(IDENTITY, NTOKEN); when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken()); when(request.getClientCertificateChain()).thenReturn(emptyList()); when(validator.validate(NTOKEN)).thenReturn(principal); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); + AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); filter.filter(request, new ResponseHandlerMock()); verify(request).setUserPrincipal(principal); } + private DiscFilterRequest createRequestMock() { + DiscFilterRequest request = mock(DiscFilterRequest.class); + when(request.getHeader("Origin")).thenReturn(ORIGIN); + return request; + } + @Test public void missing_token_and_certificate_is_unauthorized() { - DiscFilterRequest request = mock(DiscFilterRequest.class); + DiscFilterRequest request = createRequestMock(); when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(null); when(request.getClientCertificateChain()).thenReturn(emptyList()); ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); + AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); filter.filter(request, responseHandler); assertUnauthorized(responseHandler, "Unable to authenticate Athenz identity"); @@ -96,7 +107,7 @@ public class AthenzPrincipalFilterTest { @Test public void invalid_token_is_unauthorized() { - DiscFilterRequest request = mock(DiscFilterRequest.class); + DiscFilterRequest request = createRequestMock(); String errorMessage = "Invalid token"; when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken()); when(request.getClientCertificateChain()).thenReturn(emptyList()); @@ -104,7 +115,7 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); + AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); filter.filter(request, responseHandler); assertUnauthorized(responseHandler, errorMessage); @@ -112,13 +123,13 @@ public class AthenzPrincipalFilterTest { @Test public void certificate_is_accepted() { - DiscFilterRequest request = mock(DiscFilterRequest.class); + DiscFilterRequest request = createRequestMock(); when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(null); when(request.getClientCertificateChain()).thenReturn(singletonList(CERTIFICATE)); ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); + AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); filter.filter(request, responseHandler); AthenzPrincipal expectedPrincipal = new AthenzPrincipal(IDENTITY); @@ -127,7 +138,7 @@ public class AthenzPrincipalFilterTest { @Test public void both_ntoken_and_certificate_is_accepted() { - DiscFilterRequest request = mock(DiscFilterRequest.class); + DiscFilterRequest request = createRequestMock(); AthenzPrincipal principalWithToken = new AthenzPrincipal(IDENTITY, NTOKEN); when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken()); when(request.getClientCertificateChain()).thenReturn(singletonList(CERTIFICATE)); @@ -135,7 +146,7 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); + AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); filter.filter(request, responseHandler); verify(request).setUserPrincipal(principalWithToken); @@ -143,7 +154,7 @@ public class AthenzPrincipalFilterTest { @Test public void conflicting_ntoken_and_certificate_is_unauthorized() { - DiscFilterRequest request = mock(DiscFilterRequest.class); + DiscFilterRequest request = createRequestMock(); AthenzUser conflictingIdentity = AthenzUser.fromUserId("mallory"); when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken()); when(request.getClientCertificateChain()) @@ -152,7 +163,7 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER); + AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, Runnable::run, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); filter.filter(request, responseHandler); assertUnauthorized(responseHandler, "Identity in principal token does not match x509 CN"); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 49aeb334380..d4754d03997 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -54,6 +54,11 @@ public class ControllerContainerTest { " <item key=\"rotation-id-5\">rotation-fqdn-5</item>\n" + " </rotations>\n" + " </config>\n" + + " <config name=\"jdisc.http.filters.cors.cors-security-filter\">\n" + + " <allowedUrls>\n" + + " <item>http://localhost</item>\n" + + " </allowedUrls>\n" + + " </config>\n" + " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.chef.ChefMock'/>\n" + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java index 8d511b204e4..df3600eae9b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java @@ -30,6 +30,7 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.POST; import static com.yahoo.jdisc.http.HttpRequest.Method.PUT; import static com.yahoo.jdisc.http.HttpResponse.Status.FORBIDDEN; import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -143,7 +144,8 @@ public class ControllerAuthorizationFilterTest { private static ControllerAuthorizationFilter createFilter(ControllerTester controllerTester) { return new ControllerAuthorizationFilter(new AthenzClientFactoryMock(controllerTester.athenzDb()), - controllerTester.controller()); + controllerTester.controller().tenants(), + singleton("http://localhost")); } private static Optional<AuthorizationResponse> invokeFilter(ControllerAuthorizationFilter filter, |