diff options
author | Jon Marius Venstad <jvenstad@yahoo-inc.com> | 2019-03-27 12:22:18 +0100 |
---|---|---|
committer | Jon Marius Venstad <jvenstad@yahoo-inc.com> | 2019-03-28 10:24:34 +0100 |
commit | 3fcace39ee506c40545edade06245b7c1127c71b (patch) | |
tree | d2e9dbf9c91f265b99c811a8f007ca7b1117ef36 /controller-server | |
parent | 64d9c30b34dc66e85e3a140540b65532d3446d96 (diff) |
Move AthenzRoleResolver to upper level
Diffstat (limited to 'controller-server')
4 files changed, 122 insertions, 122 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java new file mode 100644 index 00000000000..38d827a78ad --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleResolver.java @@ -0,0 +1,111 @@ +package com.yahoo.vespa.hosted.controller.restapi.filter; + +import com.google.inject.Inject; +import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; +import com.yahoo.restapi.Path; +import com.yahoo.vespa.athenz.api.AthenzDomain; +import com.yahoo.vespa.athenz.api.AthenzIdentity; +import com.yahoo.vespa.athenz.api.AthenzPrincipal; +import com.yahoo.vespa.athenz.api.AthenzUser; +import com.yahoo.vespa.athenz.client.zms.ZmsClientException; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.TenantController; +import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; +import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade; +import com.yahoo.vespa.hosted.controller.role.Role; +import com.yahoo.vespa.hosted.controller.role.RoleMembership; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; +import com.yahoo.vespa.hosted.controller.tenant.UserTenant; + +import javax.ws.rs.InternalServerErrorException; +import java.security.Principal; +import java.util.Optional; + +import static com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities.SCREWDRIVER_DOMAIN; + +public class AthenzRoleResolver implements RoleMembership.Resolver { + + private final AthenzFacade athenz; + private final TenantController tenants; + private final SystemName system; + + @Inject + public AthenzRoleResolver(AthenzFacade athenz, Controller controller) { + this.athenz = athenz; + this.tenants = controller.tenants(); + this.system = controller.system(); + } + + private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) { + if (tenant instanceof AthenzTenant) { + return athenz.hasTenantAdminAccess(identity, ((AthenzTenant) tenant).domain()); + } else if (tenant instanceof UserTenant) { + if (!(identity instanceof AthenzUser)) { + return false; + } + AthenzUser user = (AthenzUser) identity; + return ((UserTenant) tenant).is(user.getName()) || isHostedOperator(identity); + } + throw new InternalServerErrorException("Unknown tenant type: " + tenant.getClass().getSimpleName()); + } + + private boolean hasDeployerAccess(AthenzIdentity identity, AthenzDomain tenantDomain, ApplicationName application) { + try { + return athenz.hasApplicationAccess(identity, + ApplicationAction.deploy, + tenantDomain, + application); + } catch (ZmsClientException e) { + throw new InternalServerErrorException("Failed to authorize operation: (" + e.getMessage() + ")", e); + } + } + + private boolean isHostedOperator(AthenzIdentity identity) { + return athenz.hasHostedOperatorAccess(identity); + } + + @Override + public RoleMembership membership(Principal principal, Optional<String> uriPath) { + if ( ! (principal instanceof AthenzPrincipal)) + throw new IllegalStateException("Expected an AthenzPrincipal to be set on the request."); + + Path path = new Path(uriPath.orElseThrow(() -> new IllegalArgumentException("This resolver needs the request path."))); + + path.matches("/application/v4/tenant/{tenant}/{*}"); + Optional<Tenant> tenant = Optional.ofNullable(path.get("tenant")).map(TenantName::from).flatMap(tenants::get); + + path.matches("/application/v4/tenant/{tenant}/application/{application}/{*}"); + Optional<ApplicationName> application = Optional.ofNullable(path.get("application")).map(ApplicationName::from); + + AthenzIdentity identity = ((AthenzPrincipal) principal).getIdentity(); + + RoleMembership.Builder memberships = RoleMembership.in(system); + if (isHostedOperator(identity)) { + memberships.add(Role.hostedOperator); + } + if (tenant.isPresent() && isTenantAdmin(identity, tenant.get())) { + memberships.add(Role.tenantAdmin).limitedTo(tenant.get().name()); + } + AthenzDomain principalDomain = identity.getDomain(); + if (principalDomain.equals(SCREWDRIVER_DOMAIN)) { + if (application.isPresent() && tenant.isPresent()) { + // NOTE: Only fine-grained deploy authorization for Athenz tenants + if (tenant.get() instanceof AthenzTenant) { + AthenzDomain tenantDomain = ((AthenzTenant) tenant.get()).domain(); + if (hasDeployerAccess(identity, tenantDomain, application.get())) { + memberships.add(Role.tenantPipelineOperator).limitedTo(tenant.get().name(), application.get()); + } + } + else { + memberships.add(Role.tenantPipelineOperator).limitedTo(tenant.get().name(), application.get()); + } + } + } + memberships.add(Role.everyone); + return memberships.build(); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java index 717704b9736..3e604732f52 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java @@ -2,48 +2,24 @@ package com.yahoo.vespa.hosted.controller.restapi.filter; import com.google.inject.Inject; -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.security.cors.CorsFilterConfig; import com.yahoo.jdisc.http.filter.security.cors.CorsRequestFilterBase; import com.yahoo.log.LogLevel; -import com.yahoo.restapi.Path; -import com.yahoo.vespa.athenz.api.AthenzDomain; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzPrincipal; -import com.yahoo.vespa.athenz.api.AthenzUser; -import com.yahoo.vespa.athenz.client.zms.ZmsClientException; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.TenantController; -import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade; import com.yahoo.vespa.hosted.controller.role.Action; -import com.yahoo.vespa.hosted.controller.role.Context; -import com.yahoo.vespa.hosted.controller.role.Role; import com.yahoo.vespa.hosted.controller.role.RoleMembership; -import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; -import com.yahoo.vespa.hosted.controller.tenant.Tenant; -import com.yahoo.vespa.hosted.controller.tenant.UserTenant; import com.yahoo.yolean.chain.After; import com.yahoo.yolean.chain.Provides; -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.WebApplicationException; import java.security.Principal; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; -import static com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities.SCREWDRIVER_DOMAIN; - /** * A security filter protects all controller apis. * @@ -55,21 +31,21 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase { private static final Logger log = Logger.getLogger(ControllerAuthorizationFilter.class.getName()); - private final AthenzFacade athenz; + private final RoleMembership.Resolver roleResolver; private final Controller controller; @Inject - public ControllerAuthorizationFilter(AthenzClientFactory clientFactory, + public ControllerAuthorizationFilter(RoleMembership.Resolver roleResolver, Controller controller, CorsFilterConfig corsConfig) { - this(clientFactory, controller, Set.copyOf(corsConfig.allowedUrls())); + this(roleResolver, controller, Set.copyOf(corsConfig.allowedUrls())); } - ControllerAuthorizationFilter(AthenzClientFactory clientFactory, + ControllerAuthorizationFilter(RoleMembership.Resolver roleResolver, Controller controller, Set<String> allowedUrls) { super(allowedUrls); - this.athenz = new AthenzFacade(clientFactory);; + this.roleResolver = roleResolver; this.controller = controller; } @@ -86,14 +62,13 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase { if (RoleMembership.everyoneIn(controller.system()).allows(action, request.getRequestURI())) return Optional.empty(); - RoleMembership roles = new AthenzRoleResolver(athenz, controller).membership(principal, - Optional.of(request.getRequestURI())); + RoleMembership roles = this.roleResolver.membership(principal, Optional.of(request.getRequestURI())); if (roles.allows(action, request.getRequestURI())) return Optional.empty(); return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Access denied")); } - catch (WebApplicationException e) { + catch (WebApplicationException e) { // TODO move to resolver. int statusCode = e.getResponse().getStatus(); String errorMessage = e.getMessage(); log.log(LogLevel.WARNING, String.format("Access denied (%d): %s", statusCode, errorMessage)); @@ -101,93 +76,4 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase { } } - // TODO: Pull class up and resolve roles from roles defined in Athenz - private static class AthenzRoleResolver implements RoleMembership.Resolver { - - private final AthenzFacade athenz; - private final TenantController tenants; - private final SystemName system; - - public AthenzRoleResolver(AthenzFacade athenz, Controller controller) { - this.athenz = athenz; - this.tenants = controller.tenants(); - this.system = controller.system(); - } - - private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) { - if (tenant instanceof AthenzTenant) { - return athenz.hasTenantAdminAccess(identity, ((AthenzTenant) tenant).domain()); - } else if (tenant instanceof UserTenant) { - if (!(identity instanceof AthenzUser)) { - return false; - } - AthenzUser user = (AthenzUser) identity; - return ((UserTenant) tenant).is(user.getName()) || isHostedOperator(identity); - } - throw new InternalServerErrorException("Unknown tenant type: " + tenant.getClass().getSimpleName()); - } - - private boolean hasDeployerAccess(AthenzIdentity identity, AthenzDomain tenantDomain, ApplicationName application) { - try { - return athenz.hasApplicationAccess(identity, - ApplicationAction.deploy, - tenantDomain, - application); - } catch (ZmsClientException e) { - throw new InternalServerErrorException("Failed to authorize operation: (" + e.getMessage() + ")", e); - } - } - - private boolean isHostedOperator(AthenzIdentity identity) { - return athenz.hasHostedOperatorAccess(identity); - } - - @Override - public RoleMembership membership(Principal principal, Optional<String> uriPath) { - if ( ! (principal instanceof AthenzPrincipal)) - throw new IllegalStateException("Expected an AthenzPrincipal to be set on the request."); - - Path path = new Path(uriPath.orElseThrow(() -> new IllegalArgumentException("This resolver needs the request path."))); - - path.matches("/application/v4/tenant/{tenant}/{*}"); - Optional<Tenant> tenant = Optional.ofNullable(path.get("tenant")).map(TenantName::from).flatMap(tenants::get); - - path.matches("/application/v4/tenant/{tenant}/application/{application}/{*}"); - Optional<ApplicationName> application = Optional.ofNullable(path.get("application")).map(ApplicationName::from); - - AthenzIdentity identity = ((AthenzPrincipal) principal).getIdentity(); - - RoleMembership.Builder memberships = RoleMembership.in(system); - if (isHostedOperator(identity)) { - memberships.add(Role.hostedOperator); - } - if (tenant.isPresent() && isTenantAdmin(identity, tenant.get())) { - memberships.add(Role.tenantAdmin).limitedTo(tenant.get().name()); - } - AthenzDomain principalDomain = identity.getDomain(); - if (principalDomain.equals(SCREWDRIVER_DOMAIN)) { - if (application.isPresent() && tenant.isPresent()) { - // NOTE: Only fine-grained deploy authorization for Athenz tenants - if (tenant.get() instanceof AthenzTenant) { - AthenzDomain tenantDomain = ((AthenzTenant) tenant.get()).domain(); - if (hasDeployerAccess(identity, tenantDomain, application.get())) { - memberships.add(Role.tenantPipelineOperator).limitedTo(tenant.get().name(), application.get()); - } - } - else { - memberships.add(Role.tenantPipelineOperator).limitedTo(tenant.get().name(), application.get()); - } - } - } - memberships.add(Role.everyone); - return memberships.build(); - } - - } - - private static Optional<AthenzPrincipal> principalFrom(DiscFilterRequest request) { - return Optional.ofNullable(request.getUserPrincipal()) - .map(AthenzPrincipal.class::cast); - } - } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index ab19ccf23d2..71b65770b1e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -95,6 +95,7 @@ public class ControllerContainerTest { " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.security.AthenzAccessControlRequests'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade'/>\n" + + " <component id='com.yahoo.vespa.hosted.controller.restapi.filter.AthenzRoleResolver'/>\n" + " <handler id='com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiHandler'>\n" + " <binding>http://*/application/v4/*</binding>\n" + " </handler>\n" + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java index e94526c7dd9..0a050b8d50d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java @@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities; +import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper; @@ -170,7 +171,8 @@ public class ControllerAuthorizationFilterTest { } private static ControllerAuthorizationFilter createFilter(ControllerTester controllerTester) { - return new ControllerAuthorizationFilter(new AthenzClientFactoryMock(controllerTester.athenzDb()), + return new ControllerAuthorizationFilter(new AthenzRoleResolver(new AthenzFacade(new AthenzClientFactoryMock(controllerTester.athenzDb())), + controllerTester.controller()), controllerTester.controller(), Set.of("http://localhost")); } |