aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc-security-filters
diff options
context:
space:
mode:
authorMorten Tokle <mortent@verizonmedia.com>2021-06-11 08:27:38 +0200
committerMorten Tokle <mortent@verizonmedia.com>2021-06-11 08:28:01 +0200
commit087856dc1977872bcb18f735383138f0144ecb35 (patch)
tree2f713042380755b1e7b17968ffa78c469bff2253 /jdisc-security-filters
parent7d4fa59ff48323503128787db7e1ec19b8a0b033 (diff)
Add request and acl mapping values as metric dimensions
Diffstat (limited to 'jdisc-security-filters')
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java77
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java13
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilterTest.java4
3 files changed, 76 insertions, 18 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 dd4b62ee494..d4e891b8d4f 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
@@ -3,12 +3,14 @@ package com.yahoo.jdisc.http.filter.security.athenz;
import com.google.inject.Inject;
import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.http.HttpRequest;
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 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.AthenzRole;
import com.yahoo.vespa.athenz.api.ZToken;
import com.yahoo.vespa.athenz.tls.AthenzX509CertificateUtils;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
@@ -56,16 +58,20 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
private final RequestResourceMapper requestResourceMapper;
private final Metric metric;
private final Set<AthenzIdentity> allowedProxyIdentities;
+ private final Optional<AthenzRole> readRole;
+ private final Optional<AthenzRole> writeRole;
@Inject
public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper, Metric metric) {
- this(config, resourceMapper, new DefaultZpe(), metric);
+ this(config, resourceMapper, new DefaultZpe(), metric, null, null);
}
public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config,
RequestResourceMapper resourceMapper,
Zpe zpe,
- Metric metric) {
+ Metric metric,
+ AthenzRole readRole,
+ AthenzRole writeRole) {
this.roleTokenHeaderName = config.roleTokenHeaderName();
List<EnabledCredentials.Enum> enabledCredentials = config.enabledCredentials();
this.enabledCredentials = enabledCredentials.isEmpty()
@@ -77,6 +83,8 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
this.allowedProxyIdentities = config.allowedProxyIdentities().stream()
.map(AthenzIdentities::from)
.collect(Collectors.toSet());
+ this.readRole = Optional.ofNullable(readRole);
+ this.writeRole = Optional.ofNullable(writeRole);
}
@Override
@@ -86,7 +94,7 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
requestResourceMapper.getResourceNameAndAction(request);
log.log(Level.FINE, () -> String.format("Resource mapping for '%s': %s", request, resourceMapping));
if (resourceMapping.isEmpty()) {
- incrementAcceptedMetrics(request, false);
+ incrementAcceptedMetrics(request, false, Optional.empty());
return Optional.empty();
}
Result result = checkAccessAllowed(request, resourceMapping.get());
@@ -94,15 +102,15 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
setAttribute(request, RESULT_ATTRIBUTE, resultType.name());
if (resultType == AuthorizationResult.Type.ALLOW) {
populateRequestWithResult(request, result);
- incrementAcceptedMetrics(request, true);
+ incrementAcceptedMetrics(request, true, Optional.of(result));
return Optional.empty();
}
log.log(Level.FINE, () -> String.format("Forbidden (403) for '%s': %s", request, resultType.name()));
- incrementRejectedMetrics(request, FORBIDDEN, resultType.name());
+ incrementRejectedMetrics(request, FORBIDDEN, resultType.name(), Optional.of(result));
return Optional.of(new ErrorResponse(FORBIDDEN, "Access forbidden: " + resultType.getDescription()));
} catch (IllegalArgumentException e) {
log.log(Level.FINE, () -> String.format("Unauthorized (401) for '%s': %s", request, e.getMessage()));
- incrementRejectedMetrics(request, UNAUTHORIZED, "Unauthorized");
+ incrementRejectedMetrics(request, UNAUTHORIZED, "Unauthorized", Optional.empty());
return Optional.of(new ErrorResponse(UNAUTHORIZED, e.getMessage()));
}
}
@@ -130,33 +138,52 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
X509Certificate identityCertificate = getClientCertificate(request).get();
AthenzIdentity peerIdentity = AthenzIdentities.from(identityCertificate);
if (allowedProxyIdentities.contains(peerIdentity)) {
- return checkAccessWithProxiedAccessToken(resourceAndAction, accessToken, identityCertificate);
+ return checkAccessWithProxiedAccessToken(request, resourceAndAction, accessToken, identityCertificate);
} else {
var zpeResult = zpe.checkAccessAllowed(
accessToken, identityCertificate, resourceAndAction.resourceName(), resourceAndAction.action());
- return new Result(ACCESS_TOKEN, peerIdentity, zpeResult);
+ return getResult(ACCESS_TOKEN, peerIdentity, zpeResult, request, resourceAndAction, mapToRequestPrivileges(accessToken.roles()));
}
}
- private Result checkAccessWithProxiedAccessToken(ResourceNameAndAction resourceAndAction, AthenzAccessToken accessToken, X509Certificate identityCertificate) {
+ private Result getResult(EnabledCredentials.Enum credentialType, AthenzIdentity identity, AuthorizationResult zpeResult, DiscFilterRequest request, ResourceNameAndAction resourceAndAction, List<String> privileges) {
+ String currentAction = resourceAndAction.action();
+ String futureAction = resourceAndAction.futureAction();
+ return new Result(credentialType, identity, zpeResult, privileges, currentAction, futureAction);
+ }
+
+ private List<String> mapToRequestPrivileges(List<AthenzRole> roles) {
+ return roles.stream()
+ .map(this::rolePrivilege)
+ .collect(Collectors.toList());
+ }
+
+ private String rolePrivilege(AthenzRole role) {
+ if (readRole.stream().anyMatch(role::equals)) return "read";
+ if (writeRole.stream().anyMatch(role::equals)) return "write";
+ return null;
+ }
+
+ private Result checkAccessWithProxiedAccessToken(DiscFilterRequest request, 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);
+ return getResult(ACCESS_TOKEN, AthenzIdentities.from(identityCertificate), zpeResult, request, resourceAndAction, mapToRequestPrivileges(accessToken.roles()));
}
private Result checkAccessWithRoleCertificate(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
X509Certificate roleCertificate = getClientCertificate(request).get();
var zpeResult = zpe.checkAccessAllowed(roleCertificate, resourceAndAction.resourceName(), resourceAndAction.action());
AthenzIdentity identity = AthenzX509CertificateUtils.getIdentityFromRoleCertificate(roleCertificate);
- return new Result(ROLE_CERTIFICATE, identity, zpeResult);
+ AthenzX509CertificateUtils.getRolesFromRoleCertificate(roleCertificate).roleName();
+ return getResult(ROLE_CERTIFICATE, identity, zpeResult, request, resourceAndAction, mapToRequestPrivileges(List.of(AthenzX509CertificateUtils.getRolesFromRoleCertificate(roleCertificate))));
}
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);
+ return getResult(ROLE_TOKEN, roleToken.getIdentity(), zpeResult, request, resourceAndAction, mapToRequestPrivileges(roleToken.getRoles()));
}
private static boolean isAccessTokenPresent(DiscFilterRequest request) {
@@ -246,20 +273,30 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
request.setAttribute(name, value);
}
- private void incrementAcceptedMetrics(DiscFilterRequest request, boolean authzRequired) {
+ private void incrementAcceptedMetrics(DiscFilterRequest request, boolean authzRequired, Optional<Result> result) {
String hostHeader = request.getHeader("Host");
Metric.Context context = metric.createContext(Map.of(
"endpoint", hostHeader != null ? hostHeader : "",
- "authz-required", Boolean.toString(authzRequired)));
+ "authz-required", Boolean.toString(authzRequired),
+ "httpMethod", HttpRequest.Method.valueOf(request.getMethod()).name(),
+ "requestPrivileges", result.map(r -> String.join(",", r.requestPrivileges)).orElse(""),
+ "currentRequestMapping", result.map(r -> r.currentAction).orElse(""),
+ "futureRequestMapping", result.map(r -> r.futureAction).orElse("")
+ ));
metric.add(ACCEPTED_METRIC_NAME, 1L, context);
}
- private void incrementRejectedMetrics(DiscFilterRequest request, int statusCode, String zpeCode) {
+ private void incrementRejectedMetrics(DiscFilterRequest request, int statusCode, String zpeCode, Optional<Result> result) {
String hostHeader = request.getHeader("Host");
Metric.Context context = metric.createContext(Map.of(
"endpoint", hostHeader != null ? hostHeader : "",
"status-code", Integer.toString(statusCode),
- "zpe-status", zpeCode));
+ "zpe-status", zpeCode,
+ "httpMethod", HttpRequest.Method.valueOf(request.getMethod()),
+ "requestPrivileges", result.map(r -> String.join(",", r.requestPrivileges)).orElse(""),
+ "currentRequestMapping", result.map(r -> r.currentAction).orElse(""),
+ "futureRequestMapping", result.map(r -> r.futureAction).orElse("")
+ ));
metric.add(REJECTED_METRIC_NAME, 1L, context);
}
@@ -267,11 +304,17 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
final EnabledCredentials.Enum credentialType;
final AthenzIdentity identity;
final AuthorizationResult zpeResult;
+ final List<String> requestPrivileges;
+ final String currentAction;
+ final String futureAction;
- Result(EnabledCredentials.Enum credentialType, AthenzIdentity identity, AuthorizationResult zpeResult) {
+ public Result(EnabledCredentials.Enum credentialType, AthenzIdentity identity, AuthorizationResult zpeResult, List<String> requestPrivileges, String currentAction, String futureAction) {
this.credentialType = credentialType;
this.identity = identity;
this.zpeResult = zpeResult;
+ this.requestPrivileges = requestPrivileges;
+ this.currentAction = currentAction;
+ this.futureAction = futureAction;
}
}
}
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java
index 56c52bd71c4..c962e973959 100644
--- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/RequestResourceMapper.java
@@ -28,10 +28,15 @@ public interface RequestResourceMapper {
class ResourceNameAndAction {
private final AthenzResourceName resourceName;
private final String action;
+ private final String futureAction;
public ResourceNameAndAction(AthenzResourceName resourceName, String action) {
+ this(resourceName, action, action);
+ }
+ public ResourceNameAndAction(AthenzResourceName resourceName, String action, String futureAction) {
this.resourceName = resourceName;
this.action = action;
+ this.futureAction = futureAction;
}
public AthenzResourceName resourceName() {
@@ -42,6 +47,14 @@ public interface RequestResourceMapper {
return action;
}
+ public ResourceNameAndAction withFutureAction(String futureAction) {
+ return new ResourceNameAndAction(resourceName, action, futureAction);
+ }
+
+ public String futureAction() {
+ return futureAction;
+ }
+
@Override
public String toString() {
return "ResourceNameAndAction{" +
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 bfe02d1f279..137e4653670 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
@@ -296,7 +296,9 @@ public class AthenzAuthorizationFilterTest {
.allowedProxyIdentities(allowedProxyIdentities)),
new StaticRequestResourceMapper(RESOURCE_NAME, ACTION),
zpe,
- metric);
+ metric,
+ new AthenzRole("domain","reader"),
+ new AthenzRole("domain", "writer"));
}
private static void assertAuthorizationResult(DiscFilterRequest request, Type expectedResult) {