summaryrefslogtreecommitdiffstats
path: root/jdisc-security-filters
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2020-01-30 12:43:37 +0100
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2020-01-30 13:37:59 +0100
commit23bdf059f1b8345495e0b72f61d30bc15761d4da (patch)
tree402eaa239832bf9b3d479b920fec6ff07ed63626 /jdisc-security-filters
parenteb4b7b8fcdc7aa5de13c05872a1fdca4076179b9 (diff)
Rewrite AthenzAuthorizationFilter to accept access tokens
Change athenz-authorization-filter.def to have an enum set of enabled credentials. Delegate to ZPE to determine if a certificate is an Athenz role or identity certificate. Introduce various request attributes to propagate result from ZPE.
Diffstat (limited to 'jdisc-security-filters')
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java199
-rw-r--r--jdisc-security-filters/src/main/resources/configdefinitions/athenz-authorization-filter.def6
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java185
3 files changed, 266 insertions, 124 deletions
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java
index 9151aa1b693..bf39ddb29b1 100644
--- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java
@@ -2,26 +2,32 @@
package com.yahoo.jdisc.http.filter.security.athenz;
import com.google.inject.Inject;
-import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
-import com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.CredentialsToVerify;
import com.yahoo.jdisc.http.filter.security.athenz.RequestResourceMapper.ResourceNameAndAction;
import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
+import com.yahoo.log.LogLevel;
+import com.yahoo.vespa.athenz.api.AthenzAccessToken;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
-import com.yahoo.vespa.athenz.api.AthenzResourceName;
-import com.yahoo.vespa.athenz.api.AthenzRole;
import com.yahoo.vespa.athenz.api.ZToken;
import com.yahoo.vespa.athenz.tls.AthenzX509CertificateUtils;
+import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.vespa.athenz.zpe.AuthorizationResult;
import com.yahoo.vespa.athenz.zpe.DefaultZpe;
import com.yahoo.vespa.athenz.zpe.Zpe;
import java.security.cert.X509Certificate;
+import java.util.EnumSet;
+import java.util.List;
import java.util.Optional;
-import java.util.function.Function;
+import java.util.logging.Logger;
-import static java.util.Collections.singletonList;
+import static com.yahoo.jdisc.Response.Status.FORBIDDEN;
+import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials.ACCESS_TOKEN;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials.ROLE_CERTIFICATE;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials.ROLE_TOKEN;
/**
* An Athenz security filter that uses a configured action and resource name to control access.
@@ -30,10 +36,18 @@ import static java.util.Collections.singletonList;
*/
public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
- private final String headerName;
+ private static final String ATTRIBUTE_PREFIX = "jdisc-security-filters.athenz-authorization-filter";
+ public static final String RESULT_ATTRIBUTE = ATTRIBUTE_PREFIX + ".result";
+ public static final String MATCHED_ROLE_ATTRIBUTE = ATTRIBUTE_PREFIX + ".matched-role";
+ public static final String IDENTITY_NAME_ATTRIBUTE = ATTRIBUTE_PREFIX + ".identity-name";
+ public static final String MATCHED_CREDENTIAL_TYPE_ATTRIBUTE = ATTRIBUTE_PREFIX + ".credentials-type";
+
+ private static final Logger log = Logger.getLogger(AthenzAuthorizationFilter.class.getName());
+
+ private final String roleTokenHeaderName;
+ private final EnumSet<EnabledCredentials.Enum> enabledCredentials;
private final Zpe zpe;
private final RequestResourceMapper requestResourceMapper;
- private final CredentialsToVerify.Enum credentialsToVerify;
@Inject
public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper) {
@@ -43,104 +57,127 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config,
RequestResourceMapper resourceMapper,
Zpe zpe) {
- this.headerName = config.roleTokenHeaderName();
- this.credentialsToVerify = config.credentialsToVerify();
+ this.roleTokenHeaderName = config.roleTokenHeaderName();
+ List<EnabledCredentials.Enum> enabledCredentials = config.enabledCredentials();
+ this.enabledCredentials = enabledCredentials.isEmpty()
+ ? EnumSet.allOf(EnabledCredentials.Enum.class)
+ : EnumSet.copyOf(enabledCredentials);
this.requestResourceMapper = resourceMapper;
this.zpe = zpe;
}
@Override
protected Optional<ErrorResponse> filter(DiscFilterRequest request) {
- Optional<ResourceNameAndAction> resourceMapping =
- requestResourceMapper.getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString());
- if (!resourceMapping.isPresent()) {
- return Optional.empty();
- }
- Optional<X509Certificate> roleCertificate = getRoleCertificate(request);
- Optional<ZToken> roleToken = getRoleToken(request, headerName);
- switch (credentialsToVerify) {
- case CERTIFICATE_ONLY: {
- if (!roleCertificate.isPresent()) {
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Missing client certificate"));
- }
- return checkAccessAllowed(roleCertificate.get(), resourceMapping.get(), request);
- }
- case TOKEN_ONLY: {
- if (!roleToken.isPresent()) {
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED,
- String.format("Role token header '%s' is missing or does not have a value.", headerName)));
- }
- return checkAccessAllowed(roleToken.get(), resourceMapping.get(), request);
- }
- case ANY: {
- if (!roleCertificate.isPresent() && !roleToken.isPresent()) {
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Both role token and role certificate is missing"));
- }
- if (roleCertificate.isPresent()) {
- return checkAccessAllowed(roleCertificate.get(), resourceMapping.get(), request);
- } else {
- return checkAccessAllowed(roleToken.get(), resourceMapping.get(), request);
- }
+ try {
+ Optional<ResourceNameAndAction> resourceMapping =
+ requestResourceMapper.getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString());
+ log.log(LogLevel.DEBUG, () -> String.format("Resource mapping for '%s': %s", request, resourceMapping));
+ if (resourceMapping.isEmpty()) {
+ return Optional.empty();
}
- default: {
- throw new IllegalStateException("Unexpected mode: " + credentialsToVerify);
+ Result result = checkAccessAllowed(request, resourceMapping.get());
+ AuthorizationResult.Type resultType = result.zpeResult.type();
+ setAttribute(request, RESULT_ATTRIBUTE, resultType.name());
+ if (resultType == AuthorizationResult.Type.ALLOW) {
+ populateRequestWithResult(request, result);
+ return Optional.empty();
}
+ log.log(LogLevel.DEBUG, () -> String.format("Forbidden (403) for '%s': %s", request, resultType.name()));
+ return Optional.of(new ErrorResponse(FORBIDDEN, "Access forbidden: " + resultType.getDescription()));
+ } catch (IllegalArgumentException e) {
+ log.log(LogLevel.DEBUG, () -> String.format("Unauthorized (401) for '%s': %s", request, e.getMessage()));
+ return Optional.of(new ErrorResponse(UNAUTHORIZED, e.getMessage()));
}
}
- private static Optional<X509Certificate> getRoleCertificate(DiscFilterRequest request) {
- return Optional.of(request.getClientCertificateChain())
- .filter(chain -> !chain.isEmpty())
- .map(chain -> chain.get(0))
- .filter(AthenzX509CertificateUtils::isAthenzRoleCertificate);
+ private Result checkAccessAllowed(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
+ // Note: the ordering of the if-constructs determines the precedence of the credential types
+ if (enabledCredentials.contains(ACCESS_TOKEN)
+ && isAccessTokenPresent(request)
+ && isClientCertificatePresent(request)) {
+ return checkAccessWithAccessToken(request, resourceAndAction);
+ } else if (enabledCredentials.contains(ROLE_CERTIFICATE)
+ && isClientCertificatePresent(request)) {
+ return checkAccessWithRoleCertificate(request, resourceAndAction);
+ } else if (enabledCredentials.contains(ROLE_TOKEN)
+ && isRoleTokenPresent(request)) {
+ return checkAccessWithRoleToken(request, resourceAndAction);
+ } else {
+ throw new IllegalArgumentException(
+ "Not authorized - request did not contain any of the allowed credentials: " + enabledCredentials);
+ }
}
- private static Optional<ZToken> getRoleToken(DiscFilterRequest request, String headerName) {
- return Optional.ofNullable(request.getHeader(headerName))
- .filter(token -> !token.isEmpty())
- .map(ZToken::new);
+ private Result checkAccessWithAccessToken(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
+ AthenzAccessToken accessToken = getAccessToken(request);
+ X509Certificate identityCertificate = getClientCertificate(request);
+ var zpeResult = zpe.checkAccessAllowed(
+ accessToken, identityCertificate, resourceAndAction.resourceName(), resourceAndAction.action());
+ return new Result(ACCESS_TOKEN, AthenzIdentities.from(identityCertificate), zpeResult);
}
- private Optional<ErrorResponse> checkAccessAllowed(X509Certificate certificate,
- ResourceNameAndAction resourceNameAndAction,
- DiscFilterRequest request) {
- return checkAccessAllowed(
- certificate, resourceNameAndAction, request, zpe::checkAccessAllowed, AthenzAuthorizationFilter::createPrincipal);
+ private Result checkAccessWithRoleCertificate(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
+ X509Certificate roleCertificate = getClientCertificate(request);
+ var zpeResult = zpe.checkAccessAllowed(roleCertificate, resourceAndAction.resourceName(), resourceAndAction.action());
+ AthenzIdentity identity = AthenzX509CertificateUtils.getIdentityFromRoleCertificate(roleCertificate);
+ return new Result(ROLE_CERTIFICATE, identity, zpeResult);
}
- private Optional<ErrorResponse> checkAccessAllowed(ZToken roleToken,
- ResourceNameAndAction resourceNameAndAction,
- DiscFilterRequest request) {
- return checkAccessAllowed(
- roleToken, resourceNameAndAction, request, zpe::checkAccessAllowed, AthenzAuthorizationFilter::createPrincipal);
+ private Result checkAccessWithRoleToken(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
+ ZToken roleToken = getRoleToken(request);
+ var zpeResult = zpe.checkAccessAllowed(roleToken, resourceAndAction.resourceName(), resourceAndAction.action());
+ return new Result(ROLE_TOKEN, roleToken.getIdentity(), zpeResult);
}
- private static <C> Optional<ErrorResponse> checkAccessAllowed(C credentials,
- ResourceNameAndAction resAndAction,
- DiscFilterRequest request,
- ZpeCheck<C> accessCheck,
- Function<C, AthenzPrincipal> principalFactory) {
- AuthorizationResult authorizationResult = accessCheck.checkAccess(credentials, resAndAction.resourceName(), resAndAction.action());
- if (authorizationResult.type() == AuthorizationResult.Type.ALLOW) {
- request.setUserPrincipal(principalFactory.apply(credentials));
- authorizationResult.matchedRole().ifPresent(role -> request.setUserRoles(new String[] {role.roleName()}));
- return Optional.empty();
- }
- return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Access forbidden: " + authorizationResult.type().getDescription()));
+ private static boolean isAccessTokenPresent(DiscFilterRequest request) {
+ return request.getHeader(AthenzAccessToken.HTTP_HEADER_NAME) != null;
+ }
+
+ private static boolean isClientCertificatePresent(DiscFilterRequest request) {
+ return !request.getClientCertificateChain().isEmpty();
}
- private static AthenzPrincipal createPrincipal(X509Certificate certificate) {
- AthenzIdentity identity = AthenzX509CertificateUtils.getIdentityFromRoleCertificate(certificate);
- AthenzRole role = AthenzX509CertificateUtils.getRolesFromRoleCertificate(certificate);
- return new AthenzPrincipal(identity, singletonList(role));
+ private boolean isRoleTokenPresent(DiscFilterRequest request) {
+ return request.getHeader(roleTokenHeaderName) != null;
}
- private static AthenzPrincipal createPrincipal(ZToken roleToken) {
- return new AthenzPrincipal(roleToken.getIdentity(), roleToken.getRoles());
+ private static AthenzAccessToken getAccessToken(DiscFilterRequest request) {
+ return new AthenzAccessToken(request.getHeader(AthenzAccessToken.HTTP_HEADER_NAME));
}
- @FunctionalInterface private interface ZpeCheck<C> {
- AuthorizationResult checkAccess(C credentials, AthenzResourceName resourceName, String action);
+ private static X509Certificate getClientCertificate(DiscFilterRequest request) {
+ return request.getClientCertificateChain().get(0);
}
+ private ZToken getRoleToken(DiscFilterRequest request) {
+ return new ZToken(request.getHeader(roleTokenHeaderName));
+ }
+
+ private static void populateRequestWithResult(DiscFilterRequest request, Result result) {
+ request.setUserPrincipal(
+ new AthenzPrincipal(result.identity, result.zpeResult.matchedRole().map(List::of).orElse(List.of())));
+ result.zpeResult.matchedRole().ifPresent(role -> {
+ request.setUserRoles(new String[]{role.roleName()});
+ setAttribute(request, MATCHED_ROLE_ATTRIBUTE, role.roleName());
+ });
+ setAttribute(request, IDENTITY_NAME_ATTRIBUTE, result.identity.getFullName());
+ setAttribute(request, MATCHED_CREDENTIAL_TYPE_ATTRIBUTE, result.credentialType.name());
+ }
+
+ private static void setAttribute(DiscFilterRequest request, String name, String value) {
+ log.log(LogLevel.DEBUG, () -> String.format("Setting attribute on '%s': '%s' = '%s'", request, name, value));
+ request.setAttribute(name, value);
+ }
+
+ private static class Result {
+ final EnabledCredentials.Enum credentialType;
+ final AthenzIdentity identity;
+ final AuthorizationResult zpeResult;
+
+ Result(EnabledCredentials.Enum credentialType, AthenzIdentity identity, AuthorizationResult zpeResult) {
+ this.credentialType = credentialType;
+ this.identity = identity;
+ this.zpeResult = zpeResult;
+ }
+ }
}
diff --git a/jdisc-security-filters/src/main/resources/configdefinitions/athenz-authorization-filter.def b/jdisc-security-filters/src/main/resources/configdefinitions/athenz-authorization-filter.def
index c60b7a125f8..ab8c4a204df 100644
--- a/jdisc-security-filters/src/main/resources/configdefinitions/athenz-authorization-filter.def
+++ b/jdisc-security-filters/src/main/resources/configdefinitions/athenz-authorization-filter.def
@@ -2,7 +2,7 @@
namespace=jdisc.http.filter.security.athenz
# Which credentials to verify. Note: ANY will prioritize token over certificate if both are present.
-credentialsToVerify enum { CERTIFICATE_ONLY, TOKEN_ONLY, ANY } default=ANY
+enabledCredentials[] enum { ROLE_CERTIFICATE, ROLE_TOKEN, ACCESS_TOKEN }
-# Name of header which includes role token. Must be set if 'credentialsTypeRequired' is set to TOKEN_ONLY or ANY.
-roleTokenHeaderName string default=""
+# Name of role token http header
+roleTokenHeaderName string default="Athenz-Role-Token"
diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java
index ecf746179a3..1fe8d73eb44 100644
--- a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java
+++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java
@@ -1,28 +1,44 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.filter.security.athenz;
-import com.yahoo.container.jdisc.RequestHandlerTestDriver;
+import com.yahoo.container.jdisc.RequestHandlerTestDriver.MockResponseHandler;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
+import com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials;
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.SubjectAlternativeName;
+import com.yahoo.security.X509CertificateBuilder;
import com.yahoo.vespa.athenz.api.AthenzAccessToken;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzResourceName;
import com.yahoo.vespa.athenz.api.AthenzRole;
import com.yahoo.vespa.athenz.api.ZToken;
+import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.vespa.athenz.zpe.AuthorizationResult;
import com.yahoo.vespa.athenz.zpe.Zpe;
import org.junit.Test;
import org.mockito.Mockito;
+import javax.security.auth.x500.X500Principal;
+import java.math.BigInteger;
+import java.security.KeyPair;
import java.security.cert.X509Certificate;
-
-import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.CredentialsToVerify.Enum.ANY;
-import static com.yahoo.vespa.athenz.zpe.AuthorizationResult.*;
-import static java.util.Collections.emptyList;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilter.MATCHED_CREDENTIAL_TYPE_ATTRIBUTE;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilter.MATCHED_ROLE_ATTRIBUTE;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilter.RESULT_ATTRIBUTE;
+import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_ECDSA;
+import static com.yahoo.security.SubjectAlternativeName.Type.RFC822_NAME;
+import static com.yahoo.vespa.athenz.zpe.AuthorizationResult.Type;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -31,73 +47,162 @@ import static org.mockito.Mockito.when;
public class AthenzAuthorizationFilterTest {
private static final AthenzResourceName RESOURCE_NAME = new AthenzResourceName("domain", "my-resource-name");
+ private static final ZToken ROLE_TOKEN = new ZToken("v=Z1;d=domain;r=my-role;p=my-domain.my-service");
+ private static final AthenzAccessToken ACCESS_TOKEN = new AthenzAccessToken("access-token");
+ private static final AthenzIdentity IDENTITY = AthenzIdentities.from("user.john");
+ private static final AthenzRole ROLE = new AthenzRole("my.domain", "my-role");
+ private static final X509Certificate IDENTITY_CERTIFICATE = createDummyIdentityCertificate(IDENTITY);
+ private static final X509Certificate ROLE_CERTIFICATE = createDummyRoleCertificate(ROLE, IDENTITY);
private static final String ACTION = "update";
private static final String HEADER_NAME = "Athenz-Role-Token";
- private static final AthenzAuthorizationFilterConfig CONFIG = createConfig();
- private static AthenzAuthorizationFilterConfig createConfig() {
- return new AthenzAuthorizationFilterConfig(
- new AthenzAuthorizationFilterConfig.Builder()
- .roleTokenHeaderName(HEADER_NAME)
- .credentialsToVerify(ANY));
+ @Test
+ public void accepts_request_with_access_token() {
+ AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of());
+
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, IDENTITY_CERTIFICATE);
+ filter.filter(request, responseHandler);
+
+ assertAuthorizationResult(request, Type.ALLOW);
+ assertRequestNotFiltered(responseHandler);
+ assertMatchedCredentialType(request, EnabledCredentials.ACCESS_TOKEN);
+ assertMatchedRole(request, ROLE);
}
@Test
- public void accepts_valid_requests() {
- AthenzAuthorizationFilter filter =
- new AthenzAuthorizationFilter(
- CONFIG, new StaticRequestResourceMapper(RESOURCE_NAME, ACTION), new AllowingZpe());
+ public void accepts_request_with_role_certificate() {
+ AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of());
- RequestHandlerTestDriver.MockResponseHandler responseHandler = new RequestHandlerTestDriver.MockResponseHandler();
- filter.filter(createRequest(), responseHandler);
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(null, null, ROLE_CERTIFICATE);
+ filter.filter(request, responseHandler);
- assertNull(responseHandler.getResponse());
+ assertAuthorizationResult(request, Type.ALLOW);
+ assertRequestNotFiltered(responseHandler);
+ assertMatchedCredentialType(request, EnabledCredentials.ROLE_CERTIFICATE);
+ assertMatchedRole(request, ROLE);
}
@Test
- public void returns_error_on_forbidden_requests() {
+ public void accepts_request_with_role_token() {
+ AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of());
+
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(ROLE_TOKEN, null, null);
+ filter.filter(request, responseHandler);
+
+ assertAuthorizationResult(request, Type.ALLOW);
+ assertRequestNotFiltered(responseHandler);
+ assertMatchedCredentialType(request, EnabledCredentials.ROLE_TOKEN);
+ assertMatchedRole(request, ROLE);
+ }
+
+ @Test
+ public void returns_unauthorized_for_request_with_disabled_credential_type() {
AthenzAuthorizationFilter filter =
- new AthenzAuthorizationFilter(
- CONFIG, new StaticRequestResourceMapper(RESOURCE_NAME, ACTION), new DenyingZpe());
+ createFilter(new AllowingZpe(), List.of(EnabledCredentials.ROLE_CERTIFICATE, EnabledCredentials.ACCESS_TOKEN));
- RequestHandlerTestDriver.MockResponseHandler responseHandler = new RequestHandlerTestDriver.MockResponseHandler();
- filter.filter(createRequest(), responseHandler);
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(ROLE_TOKEN, null, null);
+ filter.filter(request, responseHandler);
- Response response = responseHandler.getResponse();
- assertNotNull(response);
- assertEquals(403, response.getStatus());
- String content = responseHandler.readAll();
- assertThat(content, containsString(Type.DENY.getDescription()));
+ assertStatusCode(responseHandler, 401);
+ }
+
+ @Test
+ public void returns_forbidden_for_credentials_rejected_by_zpe() {
+ AthenzAuthorizationFilter filter = createFilter(new DenyingZpe(), List.of());
+
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(ROLE_TOKEN, null, null);
+ filter.filter(request, responseHandler);
+
+ assertStatusCode(responseHandler, 403);
+ assertAuthorizationResult(request, Type.DENY);
+ }
+
+ private static X509Certificate createDummyIdentityCertificate(AthenzIdentity identity) {
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ X500Principal x500Name = new X500Principal("CN="+ identity.getFullName());
+ Instant now = Instant.now();
+ return X509CertificateBuilder
+ .fromKeypair(keyPair, x500Name, now, now.plus(Duration.ofDays(30)), SHA256_WITH_ECDSA, BigInteger.ONE)
+ .build();
+ }
+
+ private static X509Certificate createDummyRoleCertificate(AthenzRole role, AthenzIdentity identity) {
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256);
+ X500Principal x500Name = new X500Principal("CN="+ role.domain().getName() + ":role." + role.roleName());
+ Instant now = Instant.now();
+ return X509CertificateBuilder
+ .fromKeypair(keyPair, x500Name, now, now.plus(Duration.ofDays(30)), SHA256_WITH_ECDSA, BigInteger.ONE)
+ .addSubjectAlternativeName(new SubjectAlternativeName(RFC822_NAME, identity.getFullName() + "@my.domain.my-identity-provider"))
+ .build();
}
- private static DiscFilterRequest createRequest() {
+ private static DiscFilterRequest createRequest(ZToken roleToken, AthenzAccessToken accessToken, X509Certificate clientCert) {
DiscFilterRequest request = Mockito.mock(DiscFilterRequest.class);
- when(request.getHeader(HEADER_NAME)).thenReturn("v=Z1;d=domain;r=my-role;p=my-domain.my-service");
+ when(request.getHeader(HEADER_NAME)).thenReturn(roleToken != null ? roleToken.getRawToken() : null);
+ when(request.getHeader(AthenzAccessToken.HTTP_HEADER_NAME)).thenReturn(accessToken != null ? "Bearer " + accessToken.value() : null);
when(request.getMethod()).thenReturn("GET");
when(request.getRequestURI()).thenReturn("/my/path");
when(request.getQueryString()).thenReturn(null);
- when(request.getClientCertificateChain()).thenReturn(emptyList());
+ when(request.getClientCertificateChain()).thenReturn(clientCert != null ? List.of(clientCert) : List.of());
return request;
}
- static class AllowingZpe implements Zpe {
+ private static AthenzAuthorizationFilter createFilter(Zpe zpe, List<EnabledCredentials.Enum> enabledCredentials) {
+ return new AthenzAuthorizationFilter(
+ new AthenzAuthorizationFilterConfig(
+ new AthenzAuthorizationFilterConfig.Builder()
+ .roleTokenHeaderName(HEADER_NAME)
+ .enabledCredentials(enabledCredentials)),
+ new StaticRequestResourceMapper(RESOURCE_NAME, ACTION),
+ zpe);
+ }
+
+ private static void assertAuthorizationResult(DiscFilterRequest request, Type expectedResult) {
+ verify(request).setAttribute(RESULT_ATTRIBUTE, expectedResult.name());
+ }
+
+ private static void assertStatusCode(MockResponseHandler responseHandler, int statusCode) {
+ Response response = responseHandler.getResponse();
+ assertThat(response, notNullValue());
+ assertThat(response.getStatus(), equalTo(statusCode));
+ }
+
+ private static void assertMatchedCredentialType(DiscFilterRequest request, EnabledCredentials.Enum expectedType) {
+ verify(request).setAttribute(MATCHED_CREDENTIAL_TYPE_ATTRIBUTE, expectedType.name());
+ }
+
+ private static void assertRequestNotFiltered(MockResponseHandler responseHandler) {
+ assertThat(responseHandler.getResponse(), nullValue());
+ }
+
+ private static void assertMatchedRole(DiscFilterRequest request, AthenzRole role) {
+ verify(request).setAttribute(MATCHED_ROLE_ATTRIBUTE, role.roleName());
+ }
+
+ private static class AllowingZpe implements Zpe {
+
@Override
public AuthorizationResult checkAccessAllowed(ZToken roleToken, AthenzResourceName resourceName, String action) {
- return new AuthorizationResult(Type.ALLOW, new AthenzRole(resourceName.getDomain(), "rolename"));
+ return new AuthorizationResult(Type.ALLOW, ROLE);
}
@Override
public AuthorizationResult checkAccessAllowed(X509Certificate roleCertificate, AthenzResourceName resourceName, String action) {
- return new AuthorizationResult(Type.ALLOW, new AthenzRole(resourceName.getDomain(), "rolename"));
+ return new AuthorizationResult(Type.ALLOW, ROLE);
}
@Override
public AuthorizationResult checkAccessAllowed(AthenzAccessToken accessToken, X509Certificate identityCertificate, AthenzResourceName resourceName, String action) {
- return new AuthorizationResult(Type.ALLOW, new AthenzRole(resourceName.getDomain(), "rolename"));
+ return new AuthorizationResult(Type.ALLOW, ROLE);
}
}
- static class DenyingZpe implements Zpe {
+ private static class DenyingZpe implements Zpe {
@Override
public AuthorizationResult checkAccessAllowed(ZToken roleToken, AthenzResourceName resourceName, String action) {
return new AuthorizationResult(Type.DENY);