summaryrefslogtreecommitdiffstats
path: root/jdisc-security-filters
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2021-02-11 15:46:05 +0100
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2021-02-11 15:46:18 +0100
commit06ea9cecdddcf44c13cf42d53b3df415c2146361 (patch)
tree29856d7589d2decdcd571b2f7731e8e656c2aa52 /jdisc-security-filters
parenta782d867784893696b3f505f547b9ccc1a5fcf2b (diff)
Athenz jdisc filter: support proxied access token from trusted peer
Diffstat (limited to 'jdisc-security-filters')
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java24
-rw-r--r--jdisc-security-filters/src/main/resources/configdefinitions/jdisc.http.filter.security.athenz.athenz-authorization-filter.def3
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java106
3 files changed, 117 insertions, 16 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 18cd6cf02c1..50369b5ede3 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
@@ -6,7 +6,6 @@ import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.jdisc.http.filter.security.athenz.RequestResourceMapper.ResourceNameAndAction;
import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
-import java.util.logging.Level;
import com.yahoo.vespa.athenz.api.AthenzAccessToken;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
@@ -22,6 +21,8 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -54,6 +55,7 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
private final Zpe zpe;
private final RequestResourceMapper requestResourceMapper;
private final Metric metric;
+ private final Set<AthenzIdentity> allowedProxyIdentities;
@Inject
public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper, Metric metric) {
@@ -72,6 +74,9 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
this.requestResourceMapper = resourceMapper;
this.zpe = zpe;
this.metric = metric;
+ this.allowedProxyIdentities = config.allowedProxyIdentities().stream()
+ .map(AthenzIdentities::from)
+ .collect(Collectors.toSet());
}
@Override
@@ -123,8 +128,21 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
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());
+ AthenzIdentity peerIdentity = AthenzIdentities.from(identityCertificate);
+ if (allowedProxyIdentities.contains(peerIdentity)) {
+ return checkAccessWithProxiedAccessToken(resourceAndAction, accessToken, identityCertificate);
+ } else {
+ var zpeResult = zpe.checkAccessAllowed(
+ accessToken, identityCertificate, resourceAndAction.resourceName(), resourceAndAction.action());
+ return new Result(ACCESS_TOKEN, peerIdentity, zpeResult);
+ }
+ }
+
+ private Result checkAccessWithProxiedAccessToken(ResourceNameAndAction resourceAndAction, AthenzAccessToken accessToken, X509Certificate identityCertificate) {
+ AthenzIdentity proxyIdentity = AthenzIdentities.from(identityCertificate);
+ log.log(Level.FINE,
+ () -> String.format("Checking proxied access token. Proxy identity: '%s'. Allowed identities: %s", proxyIdentity, allowedProxyIdentities));
+ var zpeResult = zpe.checkAccessAllowed(accessToken, resourceAndAction.resourceName(), resourceAndAction.action());
return new Result(ACCESS_TOKEN, AthenzIdentities.from(identityCertificate), zpeResult);
}
diff --git a/jdisc-security-filters/src/main/resources/configdefinitions/jdisc.http.filter.security.athenz.athenz-authorization-filter.def b/jdisc-security-filters/src/main/resources/configdefinitions/jdisc.http.filter.security.athenz.athenz-authorization-filter.def
index ab8c4a204df..dd04b4a3af8 100644
--- a/jdisc-security-filters/src/main/resources/configdefinitions/jdisc.http.filter.security.athenz.athenz-authorization-filter.def
+++ b/jdisc-security-filters/src/main/resources/configdefinitions/jdisc.http.filter.security.athenz.athenz-authorization-filter.def
@@ -6,3 +6,6 @@ enabledCredentials[] enum { ROLE_CERTIFICATE, ROLE_TOKEN, ACCESS_TOKEN }
# Name of role token http header
roleTokenHeaderName string default="Athenz-Role-Token"
+
+# Allowed Athenz proxy identities
+allowedProxyIdentities[] string
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 768c1068dfc..429f02ab42f 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
@@ -22,7 +22,6 @@ 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;
@@ -48,6 +47,10 @@ import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -59,10 +62,14 @@ 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(JWT.create().sign(Algorithm.none()));
- private static final AthenzIdentity IDENTITY = AthenzIdentities.from("user.john");
+ private static final AthenzIdentity USER_IDENTITY = AthenzIdentities.from("user.john");
+ private static final AthenzIdentity PROXY_IDENTITY = AthenzIdentities.from("proxy.service");
+ private static final AthenzIdentity UNKNOWN_IDENTITY = AthenzIdentities.from("unknown.service");
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 X509Certificate USER_IDENTITY_CERTIFICATE = createDummyIdentityCertificate(USER_IDENTITY);
+ private static final X509Certificate ROLE_CERTIFICATE = createDummyRoleCertificate(ROLE, USER_IDENTITY);
+ private static final X509Certificate PROXY_IDENTITY_CERTIFICATE = createDummyIdentityCertificate(PROXY_IDENTITY);
+ private static final X509Certificate UNKNOWN_IDENTITY_CERTIFICATE = createDummyIdentityCertificate(UNKNOWN_IDENTITY);
private static final String ACTION = "update";
private static final String HEADER_NAME = "Athenz-Role-Token";
private static final String ACCEPTED_METRIC_NAME = "jdisc.http.filter.athenz.accepted_requests";
@@ -73,7 +80,7 @@ public class AthenzAuthorizationFilterTest {
AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of());
MockResponseHandler responseHandler = new MockResponseHandler();
- DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, IDENTITY_CERTIFICATE);
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, USER_IDENTITY_CERTIFICATE);
filter.filter(request, responseHandler);
assertAuthorizationResult(request, Type.ALLOW);
@@ -111,6 +118,66 @@ public class AthenzAuthorizationFilterTest {
}
@Test
+ public void accepts_request_with_proxied_access_token() {
+ Zpe zpe = mock(Zpe.class);
+ when(zpe.checkAccessAllowed(any(), any(), any(), any())).thenReturn(new AuthorizationResult(Type.ALLOW, ROLE));
+ when(zpe.checkAccessAllowed((AthenzAccessToken) any(), any(), any())).thenReturn(new AuthorizationResult(Type.ALLOW, ROLE));
+ AthenzAuthorizationFilter filter = createFilter(zpe, List.of(), new NullMetric(), PROXY_IDENTITY);
+
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, PROXY_IDENTITY_CERTIFICATE);
+ filter.filter(request, responseHandler);
+
+ assertAuthorizationResult(request, Type.ALLOW);
+ assertRequestNotFiltered(responseHandler);
+ assertMatchedCredentialType(request, EnabledCredentials.ACCESS_TOKEN);
+ assertMatchedRole(request, ROLE);
+
+ // Verify expected checkAccessAllowed overload is invoked
+ verify(zpe, times(1)).checkAccessAllowed((AthenzAccessToken) any(), any(), any());
+ verify(zpe, never()).checkAccessAllowed(any(), any(), any(), any());
+ }
+
+ @Test
+ public void accepts_request_with_access_token_and_matching_identity_certificate_with_proxy_support_enabled() {
+ Zpe zpe = mock(Zpe.class);
+ when(zpe.checkAccessAllowed(any(), any(), any(), any())).thenReturn(new AuthorizationResult(Type.ALLOW, ROLE));
+ when(zpe.checkAccessAllowed((AthenzAccessToken) any(), any(), any())).thenReturn(new AuthorizationResult(Type.ALLOW, ROLE));
+ AthenzAuthorizationFilter filter = createFilter(zpe, List.of(), new NullMetric(), PROXY_IDENTITY);
+
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, USER_IDENTITY_CERTIFICATE);
+ filter.filter(request, responseHandler);
+
+ assertAuthorizationResult(request, Type.ALLOW);
+ assertRequestNotFiltered(responseHandler);
+ assertMatchedCredentialType(request, EnabledCredentials.ACCESS_TOKEN);
+ assertMatchedRole(request, ROLE);
+
+ // Verify expected checkAccessAllowed overload is invoked
+ verify(zpe, never()).checkAccessAllowed((AthenzAccessToken) any(), any(), any());
+ verify(zpe, times(1)).checkAccessAllowed(any(), any(), any(), any());
+ }
+
+ @Test
+ public void returns_forbidden_when_identity_certificate_has_unknown_proxy_identity() {
+ Zpe zpe = mock(Zpe.class);
+ when(zpe.checkAccessAllowed(any(), any(), any(), any())).thenReturn(new AuthorizationResult(Type.DENY, ROLE));
+ when(zpe.checkAccessAllowed((AthenzAccessToken) any(), any(), any())).thenReturn(new AuthorizationResult(Type.DENY, ROLE));
+ AthenzAuthorizationFilter filter = createFilter(zpe, List.of(), new NullMetric(), PROXY_IDENTITY);
+
+ MockResponseHandler responseHandler = new MockResponseHandler();
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, UNKNOWN_IDENTITY_CERTIFICATE);
+ filter.filter(request, responseHandler);
+
+ assertAuthorizationResult(request, Type.DENY);
+
+ // Verify expected checkAccessAllowed overload is invoked
+ verify(zpe, never()).checkAccessAllowed((AthenzAccessToken) any(), any(), any());
+ verify(zpe, times(1)).checkAccessAllowed(any(), any(), any(), any());
+ }
+
+ @Test
public void returns_unauthorized_for_request_with_disabled_credential_type() {
AthenzAuthorizationFilter filter =
createFilter(new AllowingZpe(), List.of(EnabledCredentials.ROLE_CERTIFICATE, EnabledCredentials.ACCESS_TOKEN));
@@ -139,9 +206,9 @@ public class AthenzAuthorizationFilterTest {
@Test
public void reports_metrics_for_rejected_requests() {
MetricMock metric = new MetricMock();
- AthenzAuthorizationFilter filter = createFilter(new DenyingZpe(), List.of(), metric);
+ AthenzAuthorizationFilter filter = createFilter(new DenyingZpe(), List.of(), metric, null);
MockResponseHandler responseHandler = new MockResponseHandler();
- DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, IDENTITY_CERTIFICATE);
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, USER_IDENTITY_CERTIFICATE);
filter.filter(request, responseHandler);
assertMetrics(metric, REJECTED_METRIC_NAME, Map.of("zpe-status", "DENY", "status-code", "403"));
@@ -150,9 +217,9 @@ public class AthenzAuthorizationFilterTest {
@Test
public void reports_metrics_for_accepted_requests() {
MetricMock metric = new MetricMock();
- AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of(EnabledCredentials.ACCESS_TOKEN), metric);
+ AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of(EnabledCredentials.ACCESS_TOKEN), metric, null);
MockResponseHandler responseHandler = new MockResponseHandler();
- DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, IDENTITY_CERTIFICATE);
+ DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, USER_IDENTITY_CERTIFICATE);
filter.filter(request, responseHandler);
assertMetrics(metric, ACCEPTED_METRIC_NAME, Map.of("authz-required", "true"));
@@ -190,7 +257,7 @@ public class AthenzAuthorizationFilterTest {
}
private static DiscFilterRequest createRequest(ZToken roleToken, AthenzAccessToken accessToken, X509Certificate clientCert) {
- DiscFilterRequest request = Mockito.mock(DiscFilterRequest.class);
+ DiscFilterRequest request = mock(DiscFilterRequest.class);
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");
@@ -201,15 +268,18 @@ public class AthenzAuthorizationFilterTest {
}
private static AthenzAuthorizationFilter createFilter(Zpe zpe, List<EnabledCredentials.Enum> enabledCredentials) {
- return createFilter(zpe, enabledCredentials, new NullMetric());
+ return createFilter(zpe, enabledCredentials, new NullMetric(), null);
}
- private static AthenzAuthorizationFilter createFilter(Zpe zpe, List<EnabledCredentials.Enum> enabledCredentials, Metric metric) {
+ private static AthenzAuthorizationFilter createFilter(Zpe zpe, List<EnabledCredentials.Enum> enabledCredentials,
+ Metric metric, AthenzIdentity allowedProxyIdentity) {
+ List<String> allowedProxyIdentities = allowedProxyIdentity != null ? List.of(allowedProxyIdentity.getFullName()) : List.of();
return new AthenzAuthorizationFilter(
new AthenzAuthorizationFilterConfig(
new AthenzAuthorizationFilterConfig.Builder()
.roleTokenHeaderName(HEADER_NAME)
- .enabledCredentials(enabledCredentials)),
+ .enabledCredentials(enabledCredentials)
+ .allowedProxyIdentities(allowedProxyIdentities)),
new StaticRequestResourceMapper(RESOURCE_NAME, ACTION),
zpe,
metric);
@@ -260,6 +330,11 @@ public class AthenzAuthorizationFilterTest {
public AuthorizationResult checkAccessAllowed(AthenzAccessToken accessToken, X509Certificate identityCertificate, AthenzResourceName resourceName, String action) {
return new AuthorizationResult(Type.ALLOW, ROLE);
}
+
+ @Override
+ public AuthorizationResult checkAccessAllowed(AthenzAccessToken accessToken, AthenzResourceName resourceName, String action) {
+ return new AuthorizationResult(Type.ALLOW, ROLE);
+ }
}
private static class DenyingZpe implements Zpe {
@@ -277,6 +352,11 @@ public class AthenzAuthorizationFilterTest {
public AuthorizationResult checkAccessAllowed(AthenzAccessToken accessToken, X509Certificate identityCertificate, AthenzResourceName resourceName, String action) {
return new AuthorizationResult(Type.DENY);
}
+
+ @Override
+ public AuthorizationResult checkAccessAllowed(AthenzAccessToken accessToken, AthenzResourceName resourceName, String action) {
+ return new AuthorizationResult(Type.DENY);
+ }
}
private static class MetricMock implements Metric {