From 23bdf059f1b8345495e0b72f61d30bc15761d4da Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Thu, 30 Jan 2020 12:43:37 +0100 Subject: 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. --- .../athenz/AthenzAuthorizationFilterTest.java | 185 ++++++++++++++++----- 1 file changed, 145 insertions(+), 40 deletions(-) (limited to 'jdisc-security-filters/src/test/java/com/yahoo/jdisc/http') 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) { + 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); -- cgit v1.2.3