summaryrefslogtreecommitdiffstats
path: root/controller-server/src
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@oath.com>2018-04-06 10:28:30 +0200
committerBjørn Christian Seime <bjorncs@oath.com>2018-04-09 12:49:33 +0200
commit457aad058787375f6f17fb99b263747aeddec59f (patch)
treedad84f845411ae798f4f7e158f494792ede2934a /controller-server/src
parent773bb8bf0f99fe1a2a7cec5aa026f0bbf43c7f7d (diff)
Use CorsSecurityRequestFilterBase for controller filters
Diffstat (limited to 'controller-server/src')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SecurityFilterUtils.java32
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java4
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,