summaryrefslogtreecommitdiffstats
path: root/jdisc-security-filters
diff options
context:
space:
mode:
authorMorten Tokle <mortent@verizonmedia.com>2020-03-19 14:09:14 +0100
committerMorten Tokle <mortent@verizonmedia.com>2020-03-19 15:19:30 +0100
commitcbdf4bdedcc3cf7fea09a270cda6a32df03a6fa9 (patch)
tree536258b8c13bc804c12e0d6b992be57338258e32 /jdisc-security-filters
parent1e7ebae137d6538c466ef4148d080db356746f90 (diff)
Expose metrics from filter
Diffstat (limited to 'jdisc-security-filters')
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java34
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java67
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; }
+ }
+
}