diff options
author | Morten Tokle <mortent@verizonmedia.com> | 2020-03-19 14:09:14 +0100 |
---|---|---|
committer | Morten Tokle <mortent@verizonmedia.com> | 2020-03-19 15:19:30 +0100 |
commit | cbdf4bdedcc3cf7fea09a270cda6a32df03a6fa9 (patch) | |
tree | 536258b8c13bc804c12e0d6b992be57338258e32 /jdisc-security-filters/src | |
parent | 1e7ebae137d6538c466ef4148d080db356746f90 (diff) |
Expose metrics from filter
Diffstat (limited to 'jdisc-security-filters/src')
2 files changed, 97 insertions, 4 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 81ccef651e9..53270f3b51e 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,6 +2,7 @@ package com.yahoo.jdisc.http.filter.security.athenz; import com.google.inject.Inject; +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; @@ -19,6 +20,7 @@ import com.yahoo.vespa.athenz.zpe.Zpe; import java.security.cert.X509Certificate; import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -42,6 +44,8 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { 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 String ACCEPTED_METRIC_NAME = "jdisc.http.filter.athenz.accepted_requests"; + private static final String REJECTED_METRIC_NAME = "jdisc.http.filter.athenz.rejected_requests"; private static final Logger log = Logger.getLogger(AthenzAuthorizationFilter.class.getName()); @@ -49,15 +53,17 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { private final EnumSet<EnabledCredentials.Enum> enabledCredentials; private final Zpe zpe; private final RequestResourceMapper requestResourceMapper; + private final Metric metric; @Inject - public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper) { - this(config, resourceMapper, new DefaultZpe()); + public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper, Metric metric) { + this(config, resourceMapper, new DefaultZpe(), metric); } public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper, - Zpe zpe) { + Zpe zpe, + Metric metric) { this.roleTokenHeaderName = config.roleTokenHeaderName(); List<EnabledCredentials.Enum> enabledCredentials = config.enabledCredentials(); this.enabledCredentials = enabledCredentials.isEmpty() @@ -65,6 +71,7 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { : EnumSet.copyOf(enabledCredentials); this.requestResourceMapper = resourceMapper; this.zpe = zpe; + this.metric = metric; } @Override @@ -74,6 +81,7 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { requestResourceMapper.getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString()); log.log(LogLevel.DEBUG, () -> String.format("Resource mapping for '%s': %s", request, resourceMapping)); if (resourceMapping.isEmpty()) { + incrementAcceptedMetrics(request, false); return Optional.empty(); } Result result = checkAccessAllowed(request, resourceMapping.get()); @@ -81,12 +89,15 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { setAttribute(request, RESULT_ATTRIBUTE, resultType.name()); if (resultType == AuthorizationResult.Type.ALLOW) { populateRequestWithResult(request, result); + incrementAcceptedMetrics(request, true); return Optional.empty(); } log.log(LogLevel.DEBUG, () -> String.format("Forbidden (403) for '%s': %s", request, resultType.name())); + incrementRejectedMetrics(request, FORBIDDEN, 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())); + incrementRejectedMetrics(request, UNAUTHORIZED, "Unauthorized"); return Optional.of(new ErrorResponse(UNAUTHORIZED, e.getMessage())); } } @@ -189,6 +200,23 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase { request.setAttribute(name, value); } + private void incrementAcceptedMetrics(DiscFilterRequest request, boolean authzRequired) { + String hostHeader = request.getHeader("Host"); + Metric.Context context = metric.createContext(Map.of( + "endpoint", hostHeader != null ? hostHeader : "", + "authz-required", Boolean.toString(authzRequired))); + metric.add(ACCEPTED_METRIC_NAME, 1L, context); + } + + private void incrementRejectedMetrics(DiscFilterRequest request, int statusCode, String zpeCode) { + String hostHeader = request.getHeader("Host"); + Metric.Context context = metric.createContext(Map.of( + "endpoint", hostHeader != null ? hostHeader : "", + "status-code", Integer.toString(statusCode), + "zpe-status", zpeCode)); + metric.add(REJECTED_METRIC_NAME, 1L, context); + } + private static class Result { final EnabledCredentials.Enum credentialType; final AthenzIdentity identity; 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 e8da5fab2e2..768c1068dfc 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 @@ -4,6 +4,8 @@ package com.yahoo.jdisc.http.filter.security.athenz; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.yahoo.container.jdisc.RequestHandlerTestDriver.MockResponseHandler; +import com.yahoo.docproc.jdisc.metric.NullMetric; +import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials; @@ -29,6 +31,8 @@ import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; 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; @@ -38,8 +42,11 @@ import static com.yahoo.security.SubjectAlternativeName.Type.RFC822_NAME; import static com.yahoo.vespa.athenz.zpe.AuthorizationResult.Type; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.notNullValue; 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.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,6 +65,8 @@ public class AthenzAuthorizationFilterTest { 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 String ACCEPTED_METRIC_NAME = "jdisc.http.filter.athenz.accepted_requests"; + private static final String REJECTED_METRIC_NAME = "jdisc.http.filter.athenz.rejected_requests"; @Test public void accepts_request_with_access_token() { @@ -127,6 +136,40 @@ public class AthenzAuthorizationFilterTest { assertAuthorizationResult(request, Type.DENY); } + @Test + public void reports_metrics_for_rejected_requests() { + MetricMock metric = new MetricMock(); + AthenzAuthorizationFilter filter = createFilter(new DenyingZpe(), List.of(), metric); + MockResponseHandler responseHandler = new MockResponseHandler(); + DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, IDENTITY_CERTIFICATE); + filter.filter(request, responseHandler); + + assertMetrics(metric, REJECTED_METRIC_NAME, Map.of("zpe-status", "DENY", "status-code", "403")); + } + + @Test + public void reports_metrics_for_accepted_requests() { + MetricMock metric = new MetricMock(); + AthenzAuthorizationFilter filter = createFilter(new AllowingZpe(), List.of(EnabledCredentials.ACCESS_TOKEN), metric); + MockResponseHandler responseHandler = new MockResponseHandler(); + DiscFilterRequest request = createRequest(null, ACCESS_TOKEN, IDENTITY_CERTIFICATE); + filter.filter(request, responseHandler); + + assertMetrics(metric, ACCEPTED_METRIC_NAME, Map.of("authz-required", "true")); + } + + private void assertMetrics(MetricMock metric, String metricName, Map<String, String> dimensions) { + assertThat(metric.addInvocations.keySet(), hasItem(metricName)); + SimpleMetricContext metricContext = metric.addInvocations.get(metricName); + assertNotNull("Metric not found " + metricName, metricName); + for (Map.Entry<String, String> entry : dimensions.entrySet()) { + String dimensionName = entry.getKey(); + String expected = entry.getValue(); + assertThat(metricContext.dimensions.keySet(), hasItem(dimensionName)); + assertEquals(expected, metricContext.dimensions.get(dimensionName)); + } + } + private static X509Certificate createDummyIdentityCertificate(AthenzIdentity identity) { KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); X500Principal x500Name = new X500Principal("CN="+ identity.getFullName()); @@ -158,13 +201,18 @@ public class AthenzAuthorizationFilterTest { } private static AthenzAuthorizationFilter createFilter(Zpe zpe, List<EnabledCredentials.Enum> enabledCredentials) { + return createFilter(zpe, enabledCredentials, new NullMetric()); + } + + private static AthenzAuthorizationFilter createFilter(Zpe zpe, List<EnabledCredentials.Enum> enabledCredentials, Metric metric) { return new AthenzAuthorizationFilter( new AthenzAuthorizationFilterConfig( new AthenzAuthorizationFilterConfig.Builder() .roleTokenHeaderName(HEADER_NAME) .enabledCredentials(enabledCredentials)), new StaticRequestResourceMapper(RESOURCE_NAME, ACTION), - zpe); + zpe, + metric); } private static void assertAuthorizationResult(DiscFilterRequest request, Type expectedResult) { @@ -231,4 +279,21 @@ public class AthenzAuthorizationFilterTest { } } + private static class MetricMock implements Metric { + final ConcurrentHashMap<String, SimpleMetricContext> addInvocations = new ConcurrentHashMap<>(); + + @Override public void add(String key, Number val, Context ctx) { + addInvocations.put(key, (SimpleMetricContext)ctx); + } + @Override public void set(String key, Number val, Context ctx) {} + @Override public Context createContext(Map<String, ?> properties) { return new SimpleMetricContext(properties); } + } + + private static class SimpleMetricContext implements Metric.Context { + final Map<String, String> dimensions; + + @SuppressWarnings("unchecked") + SimpleMetricContext(Map<String, ?> dimensions) { this.dimensions = (Map<String, String>)dimensions; } + } + } |