summaryrefslogtreecommitdiffstats
path: root/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java
diff options
context:
space:
mode:
Diffstat (limited to 'jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java')
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzAuthorizationFilter.java199
1 files changed, 118 insertions, 81 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 9151aa1b693..bf39ddb29b1 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,26 +2,32 @@
package com.yahoo.jdisc.http.filter.security.athenz;
import com.google.inject.Inject;
-import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
-import com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.CredentialsToVerify;
import com.yahoo.jdisc.http.filter.security.athenz.RequestResourceMapper.ResourceNameAndAction;
import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase;
+import com.yahoo.log.LogLevel;
+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.AthenzResourceName;
-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;
import com.yahoo.vespa.athenz.zpe.AuthorizationResult;
import com.yahoo.vespa.athenz.zpe.DefaultZpe;
import com.yahoo.vespa.athenz.zpe.Zpe;
import java.security.cert.X509Certificate;
+import java.util.EnumSet;
+import java.util.List;
import java.util.Optional;
-import java.util.function.Function;
+import java.util.logging.Logger;
-import static java.util.Collections.singletonList;
+import static com.yahoo.jdisc.Response.Status.FORBIDDEN;
+import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials.ACCESS_TOKEN;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials.ROLE_CERTIFICATE;
+import static com.yahoo.jdisc.http.filter.security.athenz.AthenzAuthorizationFilterConfig.EnabledCredentials.ROLE_TOKEN;
/**
* An Athenz security filter that uses a configured action and resource name to control access.
@@ -30,10 +36,18 @@ import static java.util.Collections.singletonList;
*/
public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
- private final String headerName;
+ private static final String ATTRIBUTE_PREFIX = "jdisc-security-filters.athenz-authorization-filter";
+ public static final String RESULT_ATTRIBUTE = ATTRIBUTE_PREFIX + ".result";
+ 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 Logger log = Logger.getLogger(AthenzAuthorizationFilter.class.getName());
+
+ private final String roleTokenHeaderName;
+ private final EnumSet<EnabledCredentials.Enum> enabledCredentials;
private final Zpe zpe;
private final RequestResourceMapper requestResourceMapper;
- private final CredentialsToVerify.Enum credentialsToVerify;
@Inject
public AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config, RequestResourceMapper resourceMapper) {
@@ -43,104 +57,127 @@ public class AthenzAuthorizationFilter extends JsonSecurityRequestFilterBase {
AthenzAuthorizationFilter(AthenzAuthorizationFilterConfig config,
RequestResourceMapper resourceMapper,
Zpe zpe) {
- this.headerName = config.roleTokenHeaderName();
- this.credentialsToVerify = config.credentialsToVerify();
+ this.roleTokenHeaderName = config.roleTokenHeaderName();
+ List<EnabledCredentials.Enum> enabledCredentials = config.enabledCredentials();
+ this.enabledCredentials = enabledCredentials.isEmpty()
+ ? EnumSet.allOf(EnabledCredentials.Enum.class)
+ : EnumSet.copyOf(enabledCredentials);
this.requestResourceMapper = resourceMapper;
this.zpe = zpe;
}
@Override
protected Optional<ErrorResponse> filter(DiscFilterRequest request) {
- Optional<ResourceNameAndAction> resourceMapping =
- requestResourceMapper.getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString());
- if (!resourceMapping.isPresent()) {
- return Optional.empty();
- }
- Optional<X509Certificate> roleCertificate = getRoleCertificate(request);
- Optional<ZToken> roleToken = getRoleToken(request, headerName);
- switch (credentialsToVerify) {
- case CERTIFICATE_ONLY: {
- if (!roleCertificate.isPresent()) {
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Missing client certificate"));
- }
- return checkAccessAllowed(roleCertificate.get(), resourceMapping.get(), request);
- }
- case TOKEN_ONLY: {
- if (!roleToken.isPresent()) {
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED,
- String.format("Role token header '%s' is missing or does not have a value.", headerName)));
- }
- return checkAccessAllowed(roleToken.get(), resourceMapping.get(), request);
- }
- case ANY: {
- if (!roleCertificate.isPresent() && !roleToken.isPresent()) {
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Both role token and role certificate is missing"));
- }
- if (roleCertificate.isPresent()) {
- return checkAccessAllowed(roleCertificate.get(), resourceMapping.get(), request);
- } else {
- return checkAccessAllowed(roleToken.get(), resourceMapping.get(), request);
- }
+ try {
+ Optional<ResourceNameAndAction> resourceMapping =
+ requestResourceMapper.getResourceNameAndAction(request.getMethod(), request.getRequestURI(), request.getQueryString());
+ log.log(LogLevel.DEBUG, () -> String.format("Resource mapping for '%s': %s", request, resourceMapping));
+ if (resourceMapping.isEmpty()) {
+ return Optional.empty();
}
- default: {
- throw new IllegalStateException("Unexpected mode: " + credentialsToVerify);
+ Result result = checkAccessAllowed(request, resourceMapping.get());
+ AuthorizationResult.Type resultType = result.zpeResult.type();
+ setAttribute(request, RESULT_ATTRIBUTE, resultType.name());
+ if (resultType == AuthorizationResult.Type.ALLOW) {
+ populateRequestWithResult(request, result);
+ return Optional.empty();
}
+ log.log(LogLevel.DEBUG, () -> String.format("Forbidden (403) for '%s': %s", request, 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()));
+ return Optional.of(new ErrorResponse(UNAUTHORIZED, e.getMessage()));
}
}
- private static Optional<X509Certificate> getRoleCertificate(DiscFilterRequest request) {
- return Optional.of(request.getClientCertificateChain())
- .filter(chain -> !chain.isEmpty())
- .map(chain -> chain.get(0))
- .filter(AthenzX509CertificateUtils::isAthenzRoleCertificate);
+ private Result checkAccessAllowed(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
+ // Note: the ordering of the if-constructs determines the precedence of the credential types
+ if (enabledCredentials.contains(ACCESS_TOKEN)
+ && isAccessTokenPresent(request)
+ && isClientCertificatePresent(request)) {
+ return checkAccessWithAccessToken(request, resourceAndAction);
+ } else if (enabledCredentials.contains(ROLE_CERTIFICATE)
+ && isClientCertificatePresent(request)) {
+ return checkAccessWithRoleCertificate(request, resourceAndAction);
+ } else if (enabledCredentials.contains(ROLE_TOKEN)
+ && isRoleTokenPresent(request)) {
+ return checkAccessWithRoleToken(request, resourceAndAction);
+ } else {
+ throw new IllegalArgumentException(
+ "Not authorized - request did not contain any of the allowed credentials: " + enabledCredentials);
+ }
}
- private static Optional<ZToken> getRoleToken(DiscFilterRequest request, String headerName) {
- return Optional.ofNullable(request.getHeader(headerName))
- .filter(token -> !token.isEmpty())
- .map(ZToken::new);
+ 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());
+ return new Result(ACCESS_TOKEN, AthenzIdentities.from(identityCertificate), zpeResult);
}
- private Optional<ErrorResponse> checkAccessAllowed(X509Certificate certificate,
- ResourceNameAndAction resourceNameAndAction,
- DiscFilterRequest request) {
- return checkAccessAllowed(
- certificate, resourceNameAndAction, request, zpe::checkAccessAllowed, AthenzAuthorizationFilter::createPrincipal);
+ private Result checkAccessWithRoleCertificate(DiscFilterRequest request, ResourceNameAndAction resourceAndAction) {
+ X509Certificate roleCertificate = getClientCertificate(request);
+ var zpeResult = zpe.checkAccessAllowed(roleCertificate, resourceAndAction.resourceName(), resourceAndAction.action());
+ AthenzIdentity identity = AthenzX509CertificateUtils.getIdentityFromRoleCertificate(roleCertificate);
+ return new Result(ROLE_CERTIFICATE, identity, zpeResult);
}
- private Optional<ErrorResponse> checkAccessAllowed(ZToken roleToken,
- ResourceNameAndAction resourceNameAndAction,
- DiscFilterRequest request) {
- return checkAccessAllowed(
- roleToken, resourceNameAndAction, request, zpe::checkAccessAllowed, AthenzAuthorizationFilter::createPrincipal);
+ 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);
}
- private static <C> Optional<ErrorResponse> checkAccessAllowed(C credentials,
- ResourceNameAndAction resAndAction,
- DiscFilterRequest request,
- ZpeCheck<C> accessCheck,
- Function<C, AthenzPrincipal> principalFactory) {
- AuthorizationResult authorizationResult = accessCheck.checkAccess(credentials, resAndAction.resourceName(), resAndAction.action());
- if (authorizationResult.type() == AuthorizationResult.Type.ALLOW) {
- request.setUserPrincipal(principalFactory.apply(credentials));
- authorizationResult.matchedRole().ifPresent(role -> request.setUserRoles(new String[] {role.roleName()}));
- return Optional.empty();
- }
- return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Access forbidden: " + authorizationResult.type().getDescription()));
+ private static boolean isAccessTokenPresent(DiscFilterRequest request) {
+ return request.getHeader(AthenzAccessToken.HTTP_HEADER_NAME) != null;
+ }
+
+ private static boolean isClientCertificatePresent(DiscFilterRequest request) {
+ return !request.getClientCertificateChain().isEmpty();
}
- private static AthenzPrincipal createPrincipal(X509Certificate certificate) {
- AthenzIdentity identity = AthenzX509CertificateUtils.getIdentityFromRoleCertificate(certificate);
- AthenzRole role = AthenzX509CertificateUtils.getRolesFromRoleCertificate(certificate);
- return new AthenzPrincipal(identity, singletonList(role));
+ private boolean isRoleTokenPresent(DiscFilterRequest request) {
+ return request.getHeader(roleTokenHeaderName) != null;
}
- private static AthenzPrincipal createPrincipal(ZToken roleToken) {
- return new AthenzPrincipal(roleToken.getIdentity(), roleToken.getRoles());
+ private static AthenzAccessToken getAccessToken(DiscFilterRequest request) {
+ return new AthenzAccessToken(request.getHeader(AthenzAccessToken.HTTP_HEADER_NAME));
}
- @FunctionalInterface private interface ZpeCheck<C> {
- AuthorizationResult checkAccess(C credentials, AthenzResourceName resourceName, String action);
+ private static X509Certificate getClientCertificate(DiscFilterRequest request) {
+ return request.getClientCertificateChain().get(0);
}
+ private ZToken getRoleToken(DiscFilterRequest request) {
+ return new ZToken(request.getHeader(roleTokenHeaderName));
+ }
+
+ private static void populateRequestWithResult(DiscFilterRequest request, Result result) {
+ request.setUserPrincipal(
+ new AthenzPrincipal(result.identity, result.zpeResult.matchedRole().map(List::of).orElse(List.of())));
+ result.zpeResult.matchedRole().ifPresent(role -> {
+ request.setUserRoles(new String[]{role.roleName()});
+ setAttribute(request, MATCHED_ROLE_ATTRIBUTE, role.roleName());
+ });
+ setAttribute(request, IDENTITY_NAME_ATTRIBUTE, result.identity.getFullName());
+ setAttribute(request, MATCHED_CREDENTIAL_TYPE_ATTRIBUTE, result.credentialType.name());
+ }
+
+ private static void setAttribute(DiscFilterRequest request, String name, String value) {
+ log.log(LogLevel.DEBUG, () -> String.format("Setting attribute on '%s': '%s' = '%s'", request, name, value));
+ request.setAttribute(name, value);
+ }
+
+ private static class Result {
+ final EnabledCredentials.Enum credentialType;
+ final AthenzIdentity identity;
+ final AuthorizationResult zpeResult;
+
+ Result(EnabledCredentials.Enum credentialType, AthenzIdentity identity, AuthorizationResult zpeResult) {
+ this.credentialType = credentialType;
+ this.identity = identity;
+ this.zpeResult = zpeResult;
+ }
+ }
}