diff options
author | Jon Marius Venstad <venstad@gmail.com> | 2019-04-04 11:02:13 +0200 |
---|---|---|
committer | Jon Marius Venstad <venstad@gmail.com> | 2019-04-04 11:02:13 +0200 |
commit | 7e453002c4957397f20e0bd9e31651da674ddf7c (patch) | |
tree | ff0f8830d4f519378b384b3d5649156e55378180 /controller-server | |
parent | 77433506fc9af7a0b17a43a0e14ad6b0f1ea8594 (diff) |
Move role package to controller-api
Diffstat (limited to 'controller-server')
18 files changed, 13 insertions, 935 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java index 1a3498e4abd..fee6bb7c44c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java @@ -18,9 +18,9 @@ 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.role.RolePrincipal; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.RoleMembership; +import com.yahoo.vespa.hosted.controller.api.role.RolePrincipal; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import com.yahoo.vespa.hosted.controller.tenant.UserTenant; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/Auth0RoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/Auth0RoleFilter.java index 9596e1d10f9..25d5b72b7e4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/Auth0RoleFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/Auth0RoleFilter.java @@ -2,8 +2,8 @@ package com.yahoo.vespa.hosted.controller.restapi.filter; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase; -import com.yahoo.vespa.hosted.controller.role.RoleMembership; -import com.yahoo.vespa.hosted.controller.role.RolePrincipal; +import com.yahoo.vespa.hosted.controller.api.role.RoleMembership; +import com.yahoo.vespa.hosted.controller.api.role.RolePrincipal; import java.util.Objects; import java.util.Optional; 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 ecf6cd802ba..8c473bcaaec 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 @@ -10,10 +10,10 @@ 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.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.role.Action; -import com.yahoo.vespa.hosted.controller.role.Role; -import com.yahoo.vespa.hosted.controller.role.RoleMembership; -import com.yahoo.vespa.hosted.controller.role.RolePrincipal; +import com.yahoo.vespa.hosted.controller.api.role.Action; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.RoleMembership; +import com.yahoo.vespa.hosted.controller.api.role.RolePrincipal; import java.security.Principal; import java.util.Optional; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Action.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Action.java deleted file mode 100644 index 533c28905a9..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Action.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.jdisc.http.HttpRequest; - -import java.util.EnumSet; - -/** - * Action defines an operation, typically a HTTP method, that may be performed on an entity in the controller - * (e.g. tenant or application). - * - * @author mpolden - */ -public enum Action { - - create, - read, - update, - delete; - - public static EnumSet<Action> all() { - return EnumSet.allOf(Action.class); - } - - public static EnumSet<Action> write() { - return EnumSet.of(create, update, delete); - } - - /** Returns the appropriate action for given HTTP method */ - public static Action from(HttpRequest.Method method) { - switch (method) { - case POST: return Action.create; - case GET: - case OPTIONS: - case HEAD: return Action.read; - case PUT: - case PATCH: return Action.update; - case DELETE: return Action.delete; - default: throw new IllegalArgumentException("No action defined for method " + method); - } - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Context.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Context.java deleted file mode 100644 index 71452a3ef20..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Context.java +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; - -import java.util.Objects; -import java.util.Optional; - -/** - * The context in which a role is valid. - * - * @author mpolden - */ -public class Context { - - private final Optional<TenantName> tenant; - private final Optional<ApplicationName> application; - private final SystemName system; - - private Context(Optional<TenantName> tenant, Optional<ApplicationName> application, SystemName system) { - this.tenant = Objects.requireNonNull(tenant, "tenant must be non-null"); - this.application = Objects.requireNonNull(application, "application must be non-null"); - this.system = Objects.requireNonNull(system, "system must be non-null"); - } - - /** A specific tenant this is valid for, if any */ - public Optional<TenantName> tenant() { - return tenant; - } - - /** A specific application this is valid for, if any */ - public Optional<ApplicationName> application() { - return application; - } - - /** System in which this is valid */ - public SystemName system() { - return system; - } - - /** Returns whether this context is considered limited */ - public boolean limited() { - return tenant.isPresent() || application.isPresent(); - } - - /** Returns a context that has no restrictions on tenant or application in given system */ - public static Context unlimitedIn(SystemName system) { - return new Context(Optional.empty(), Optional.empty(), system); - } - - /** Returns a context that is limited to given tenant and system */ - public static Context limitedTo(TenantName tenant, SystemName system) { - return new Context(Optional.of(tenant), Optional.empty(), system); - } - - /** Returns a context that is limited to given tenant, application and system */ - public static Context limitedTo(TenantName tenant, ApplicationName application, SystemName system) { - return new Context(Optional.of(tenant), Optional.of(application), system); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Context context = (Context) o; - return tenant.equals(context.tenant) && - application.equals(context.application) && - system == context.system; - } - - @Override - public int hashCode() { - return Objects.hash(tenant, application, system); - } - - @Override - public String toString() { - return "tenant " + tenant.map(TenantName::value).orElse("[none]") + ", application " + - application.map(ApplicationName::value).orElse("[none]") + ", system " + system; - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java deleted file mode 100644 index 9dbfdbb8dac..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/PathGroup.java +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.restapi.Path; - -import java.net.URI; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -/** - * This declares and groups all known REST API paths in the controller. - * - * When creating a new API, its paths must be added here and a policy must be declared in {@link Policy}. - * - * @author mpolden - */ -public enum PathGroup { - - /** Paths used for system management by operators. */ - operator("/controller/v1/{*}", - "/flags/v1/{*}", - "/nodes/v2/{*}", - "/orchestrator/v1/{*}", - "/os/v1/{*}", - "/provision/v2/{*}", - "/zone/v2/{*}"), - - /** Paths used for user management. */ - userManagement("/user/v1/{*}"), // TODO probably add tenant and application levels. - - /** Paths used for creating user tenants. */ - user("/application/v4/user"), - - /** Paths used for creating tenants with proper access control. */ - tenant(Matcher.tenant, - "/application/v4/tenant/{tenant}"), - - /** Paths used by tenant administrators. */ - tenantInfo(Matcher.tenant, - "/application/v4/tenant/{tenant}/application/"), - - /** Path for the base application resource. */ - application(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}"), - - /** Paths used by application administrators. */ - applicationInfo(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}/deploying/{*}", - "/application/v4/tenant/{tenant}/application/{application}/instance/{*}", - "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs", - "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/suspended", - "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/service/{*}", - "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/{*}"), - - /** Path used to restart application nodes. */ // TODO move to the above when everyone is on new pipeline. - applicationRestart(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{ignored}/restart"), - /** Paths used for development deployments. */ - developmentDeployment(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}/environment/dev/region/{region}/instance/{instance}", - "/application/v4/tenant/{tenant}/application/{application}/environment/dev/region/{region}/instance/{instance}/deploy", - "/application/v4/tenant/{tenant}/application/{application}/environment/perf/region/{region}/instance/{instance}", - "/application/v4/tenant/{tenant}/application/{application}/environment/perf/region/{region}/instance/{instance}/deploy"), - - /** Paths used for production deployments. */ - productionDeployment(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}", - "/application/v4/tenant/{tenant}/application/{application}/environment/prod/region/{region}/instance/{instance}/deploy", - "/application/v4/tenant/{tenant}/application/{application}/environment/test/region/{region}/instance/{instance}", - "/application/v4/tenant/{tenant}/application/{application}/environment/test/region/{region}/instance/{instance}/deploy", - "/application/v4/tenant/{tenant}/application/{application}/environment/staging/region/{region}/instance/{instance}", - "/application/v4/tenant/{tenant}/application/{application}/environment/staging/region/{region}/instance/{instance}/deploy"), - - /** Paths used for continuous deployment to production. */ - submission(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}/submit"), - - /** Paths used for other tasks by build services. */ // TODO: This will vanish. - buildService(Matcher.tenant, - Matcher.application, - "/application/v4/tenant/{tenant}/application/{application}/jobreport", - "/application/v4/tenant/{tenant}/application/{application}/promote", - "/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/promote"), - - /** Paths which contain (not very strictly) classified information about, e.g., customers. */ - classifiedInfo("/athenz/v1/{*}", - "/cost/v1/{*}", - "/deployment/v1/{*}", - "/application/v4/", - "/application/v4/tenant/", - "/", - "/d/{*}", - "/statuspage/v1/{*}" - ), - - /** Paths providing public information. */ - publicInfo("/badge/v1/{*}", - "/zone/v1/{*}"); - - final List<String> pathSpecs; - final List<Matcher> matchers; - - PathGroup(String... pathSpecs) { - this(List.of(), List.of(pathSpecs)); - } - - PathGroup(Matcher first, String... pathSpecs) { - this(List.of(first), List.of(pathSpecs)); - } - - PathGroup(Matcher first, Matcher second, String... pathSpecs) { - this(List.of(first, second), List.of(pathSpecs)); - } - - /** Creates a new path group, if the given context matchers are each present exactly once in each of the given specs. */ - PathGroup(List<Matcher> matchers, List<String> pathSpecs) { - this.matchers = matchers; - this.pathSpecs = pathSpecs; - } - - /** Returns path if it matches any spec in this group, with match groups set by the match. */ - @SuppressWarnings("deprecation") - private Optional<Path> get(URI uri) { - Path matcher = new Path(uri); // TODO Get URI down here. - for (String spec : pathSpecs) // Iterate to be sure the Path's state is that of the match. - if (matcher.matches(spec)) return Optional.of(matcher); - return Optional.empty(); - } - - /** All known path groups */ - public static Set<PathGroup> all() { - return EnumSet.allOf(PathGroup.class); - } - - /** Returns whether this group matches path in given context */ - public boolean matches(URI uri, Context context) { - return get(uri).map(p -> { - boolean match = true; - String tenant = p.get(Matcher.tenant.name); - if (tenant != null && context.tenant().isPresent()) { - match = context.tenant().get().value().equals(tenant); - } - String application = p.get(Matcher.application.name); - if (application != null && context.application().isPresent()) { - match &= context.application().get().value().equals(application); - } - return match; - }).orElse(false); - } - - - /** Fragments used to match parts of a path to create a context. */ - enum Matcher { - - tenant("{tenant}"), - application("{application}"); - - final String pattern; - final String name; - - Matcher(String pattern) { - this.pattern = pattern; - this.name = pattern.substring(1, pattern.length() - 1); - } - - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java deleted file mode 100644 index 719f90b9fa4..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Policy.java +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; - -import java.net.URI; -import java.util.Set; - -/** - * Policies for REST APIs in the controller. A policy is only considered when defined in a {@link Role}. - * A policy describes a set of {@link Privilege}s, which are valid for a set of {@link SystemName}s. - * A policy is evaluated with a {@link Context}, which provides the {@link SystemName} the policy is - * evaluated in, and any limitations to a specific {@link TenantName} or {@link ApplicationName}. - * - * @author mpolden - */ -public enum Policy { - - /** Full access to everything. */ - operator(Privilege.grant(Action.all()) - .on(PathGroup.all()) - .in(SystemName.all())), - - /** Full access to user management in select systems. */ - manager(Privilege.grant(Action.all()) - .on(PathGroup.userManagement) - .in(SystemName.Public)), - - /** Access to create a user tenant in select systems. */ - userCreate(Privilege.grant(Action.update) - .on(PathGroup.user) - .in(SystemName.main, SystemName.cd, SystemName.dev)), - - /** Access to create a tenant in select systems. */ - tenantCreate(Privilege.grant(Action.create) - .on(PathGroup.tenant) - .in(SystemName.main, SystemName.cd, SystemName.dev)), // TODO SystemName.all() - - /** Full access to tenant information and settings. */ - tenantDelete(Privilege.grant(Action.delete) - .on(PathGroup.tenant) - .in(SystemName.all())), - - /** Full access to tenant information and settings. */ - tenantUpdate(Privilege.grant(Action.update) - .on(PathGroup.tenant) - .in(SystemName.all())), - - /** Read access to tenant information and settings. */ - tenantRead(Privilege.grant(Action.read) - .on(PathGroup.tenant, PathGroup.tenantInfo) - .in(SystemName.all())), - - /** Access to create application under a certain tenant. */ - applicationCreate(Privilege.grant(Action.create) - .on(PathGroup.application) - .in(SystemName.all())), - - /** Read access to application information and settings. */ - applicationRead(Privilege.grant(Action.read) - .on(PathGroup.application, PathGroup.applicationInfo) - .in(SystemName.all())), - - /** Read access to application information and settings. */ - applicationUpdate(Privilege.grant(Action.update) - .on(PathGroup.application, PathGroup.applicationInfo) - .in(SystemName.all())), - - /** Access to delete a certain application. */ - applicationDelete(Privilege.grant(Action.delete) - .on(PathGroup.application) - .in(SystemName.all())), - - /** Full access to application information and settings. */ - applicationOperations(Privilege.grant(Action.write()) - .on(PathGroup.applicationInfo, PathGroup.applicationRestart) - .in(SystemName.all())), - - /** Full access to application development deployments. */ - developmentDeployment(Privilege.grant(Action.all()) - .on(PathGroup.developmentDeployment) - .in(SystemName.all())), - - /** Full access to application production deployments. */ - productionDeployment(Privilege.grant(Action.all()) - .on(PathGroup.productionDeployment) - .in(SystemName.all())), - - /** Read access to all application deployments. */ - deploymentRead(Privilege.grant(Action.read) - .on(PathGroup.developmentDeployment, PathGroup.productionDeployment) - .in(SystemName.all())), - - /** Full access to submissions for continuous deployment. */ - submission(Privilege.grant(Action.all()) - .on(PathGroup.submission) - .in(SystemName.all())), - - /** Full access to the additional tasks needed for continuous deployment. */ - deploymentPipeline(Privilege.grant(Action.all()) // TODO remove when everyone is on new pipeline. - .on(PathGroup.buildService, PathGroup.applicationRestart) - .in(SystemName.all())), - - /** Read access to all information in select systems. */ - classifiedRead(Privilege.grant(Action.read) - .on(PathGroup.all()) - .in(SystemName.main, SystemName.cd, SystemName.dev)), - - /** Read access to public info. */ - publicRead(Privilege.grant(Action.read) - .on(PathGroup.publicInfo) - .in(SystemName.all())); - - private final Set<Privilege> privileges; - - Policy(Privilege... privileges) { - this.privileges = Set.of(privileges); - } - - /** Returns whether action is allowed on path in given context */ - public boolean evaluate(Action action, URI uri, Context context) { - return privileges.stream().anyMatch(privilege -> privilege.actions().contains(action) && - privilege.systems().contains(context.system()) && - privilege.pathGroups().stream() - .anyMatch(pg -> pg.matches(uri, context))); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Privilege.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Privilege.java deleted file mode 100644 index 4c5ad136f56..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Privilege.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.SystemName; - -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.Objects; -import java.util.Set; - -/** - * This describes a privilege in the controller. A privilege expresses the actions (e.g. create or read) granted - * for a particular group of REST API paths. A privilege is valid in one or more systems. - * - * @author mpolden - */ -public class Privilege { - - private final Set<SystemName> systems; - private final Set<Action> actions; - private final Set<PathGroup> pathGroups; - - private Privilege(Set<SystemName> systems, Set<Action> actions, Set<PathGroup> pathGroups) { - this.systems = EnumSet.copyOf(Objects.requireNonNull(systems, "system must be non-null")); - this.actions = EnumSet.copyOf(Objects.requireNonNull(actions, "actions must be non-null")); - this.pathGroups = EnumSet.copyOf(Objects.requireNonNull(pathGroups, "pathGroups must be non-null")); - if (systems.isEmpty()) { - throw new IllegalArgumentException("systems must be non-empty"); - } - } - - /** Systems where this applies */ - public Set<SystemName> systems() { - return systems; - } - - /** Actions allowed by this */ - public Set<Action> actions() { - return actions; - } - - /** Path groups where this applies */ - public Set<PathGroup> pathGroups() { - return pathGroups; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Privilege privilege = (Privilege) o; - return systems.equals(privilege.systems) && - actions.equals(privilege.actions) && - pathGroups.equals(privilege.pathGroups); - } - - @Override - public int hashCode() { - return Objects.hash(systems, actions, pathGroups); - } - - public static PrivilegeBuilder grant(Action... actions) { - return grant(Set.of(actions)); - } - - public static PrivilegeBuilder grant(Set<Action> actions) { - return new PrivilegeBuilder(actions); - } - - public static class PrivilegeBuilder { - - private Set<Action> actions; - private Set<PathGroup> pathGroups; - - private PrivilegeBuilder(Set<Action> actions) { - this.actions = EnumSet.copyOf(actions); - this.pathGroups = new LinkedHashSet<>(); - } - - public PrivilegeBuilder on(PathGroup... pathGroups) { - return on(Set.of(pathGroups)); - } - - public PrivilegeBuilder on(Set<PathGroup> pathGroups) { - this.pathGroups.addAll(pathGroups); - return this; - } - - public Privilege in(SystemName... systems) { - return in(Set.of(systems)); - } - - public Privilege in(Set<SystemName> systems) { - return new Privilege(systems, actions, pathGroups); - } - - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java deleted file mode 100644 index d3f43ad895f..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/Role.java +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import java.net.URI; -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; - -import java.util.EnumSet; -import java.util.Map; -import java.util.Set; - -/** - * This declares all tenant roles known to the controller. A role contains one or more {@link Policy}s which decide - * what actions a member of a role can perform. - * - * Optionally, some role definition also inherit all policies from a "lower ranking" role. Read the list of roles - * from {@code everyone} to {@code tenantAdmin}, in order, to see what policies these roles. - * - * @author mpolden - * @author jonmv - */ -public class Role implements RoleInSystem, RoleInSystemWithTenant, RoleInSystemWithTenantAndApplication { - - /** Deus ex machina. */ - public static final RoleInSystem hostedOperator = new Role(Policy.operator); - - /** Build service which may submit new applications for continuous deployment. */ - public static final RoleInSystemWithTenantAndApplication buildService = new Role(Policy.submission, - Policy.applicationRead); - - /** Base role which every user is part of. */ - public static final RoleInSystem everyone = new Role(Policy.classifiedRead, - Policy.publicRead, - Policy.userCreate, - Policy.tenantCreate); - - /** Application reader which can see all information about an application, its tenant and deployments. */ - public static final RoleInSystemWithTenantAndApplication applicationReader = new Role(everyone, - Policy.tenantRead, - Policy.applicationRead, - Policy.deploymentRead); - - /** Application developer with access to deploy to development zones. */ - public static final RoleInSystemWithTenantAndApplication applicationDeveloper = new Role(applicationReader, - Policy.developmentDeployment); - - /** Application operator with access to normal, operational tasks of an application. */ - public static final RoleInSystemWithTenantAndApplication applicationOperator = new Role(applicationDeveloper, - Policy.applicationOperations); - - /** Application administrator with full access to an already existing application, including emergency operations. */ - public static final RoleInSystemWithTenantAndApplication applicationAdmin = new Role(applicationOperator, - Policy.applicationUpdate, - Policy.productionDeployment, - Policy.submission); - - /** Application administrator with the additional ability to delete an application. */ - public static final RoleInSystemWithTenantAndApplication applicationOwner = new Role(applicationOperator, - Policy.applicationDelete); - - /** Tenant operator with admin access to all applications under the tenant, as well as the ability to create applications. */ - public static final RoleInSystemWithTenant tenantOperator = new Role(applicationAdmin, - Policy.applicationCreate); - - /** Tenant admin with full access to all tenant resources, except deleting the tenant. */ - public static final RoleInSystemWithTenant tenantAdmin = new Role(tenantOperator, - Policy.applicationDelete, - Policy.manager, - Policy.tenantUpdate); - - /** Tenant admin with full access to all tenant resources. */ - public static final RoleInSystemWithTenant tenantOwner = new Role(tenantAdmin, - Policy.tenantDelete); - - /** Build and continuous delivery service. */ // TODO replace with buildService, when everyone is on new pipeline. - public static final RoleInSystemWithTenantAndApplication tenantPipeline = new Role(everyone, - Policy.submission, - Policy.deploymentPipeline, - Policy.productionDeployment); - - /** Tenant administrator with full access to all child resources. */ - public static final RoleInSystemWithTenant athenzTenantAdmin = new Role(everyone, - Policy.tenantRead, - Policy.tenantUpdate, - Policy.tenantDelete, - Policy.applicationCreate, - Policy.applicationUpdate, - Policy.applicationDelete, - Policy.applicationOperations, - Policy.developmentDeployment); - - private final Set<Policy> policies; - - private Role(Policy... policies) { - this.policies = EnumSet.copyOf(Set.of(policies)); - } - - private Role(Object inherited, Policy... policies) { - this.policies = EnumSet.copyOf(Set.of(policies)); - this.policies.addAll(((Role) inherited).policies); - } - - /** - * Returns whether this role is allowed to perform action in given role context. Action is allowed if at least one - * policy evaluates to true. - */ - public boolean allows(Action action, URI uri, Context context) { - return policies.stream().anyMatch(policy -> policy.evaluate(action, uri, context)); - } - - @Override - public RoleMembership limitedTo(SystemName system) { - return new RoleMembership(Map.of(this, Set.of(Context.unlimitedIn(system)))); - } - - @Override - public RoleMembership limitedTo(TenantName tenant, SystemName system) { - return new RoleMembership(Map.of(this, Set.of(Context.limitedTo(tenant, system)))); - } - - @Override - public RoleMembership limitedTo(ApplicationName application, TenantName tenant, SystemName system) { - return new RoleMembership(Map.of(this, Set.of(Context.limitedTo(tenant, application, system)))); - } - -} - diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystem.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystem.java deleted file mode 100644 index 8f5401b2993..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystem.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.SystemName; - -/** A role which requires only the context of a system. */ -public interface RoleInSystem { - RoleMembership limitedTo(SystemName system); -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystemWithTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystemWithTenant.java deleted file mode 100644 index c5bba56b640..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystemWithTenant.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; - -/** A role which requires the context of a system and a tenant. */ -public interface RoleInSystemWithTenant { - RoleMembership limitedTo(TenantName tenant, SystemName system); -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystemWithTenantAndApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystemWithTenantAndApplication.java deleted file mode 100644 index cb4b0bd4dd8..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleInSystemWithTenantAndApplication.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; - -/** A role which requires the context of a system, a tenant, and an application. */ -public interface RoleInSystemWithTenantAndApplication { - RoleMembership limitedTo(ApplicationName application, TenantName tenant, SystemName system); -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java deleted file mode 100644 index e0311bebbba..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RoleMembership.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import java.net.URI; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * A list of roles and their associated contexts. This defines the role membership of a tenant, and in which contexts - * (see {@link Context}) those roles apply. - * - * @author mpolden - * @author jonmv - */ -public class RoleMembership { - - private final Map<Role, Set<Context>> roles; - - RoleMembership(Map<Role, Set<Context>> roles) { - this.roles = roles.entrySet().stream() - .collect(Collectors.toUnmodifiableMap(entry -> entry.getKey(), - entry -> Set.copyOf(entry.getValue()))); - } - - public RoleMembership and(RoleMembership other) { - return new RoleMembership(Stream.concat(this.roles.entrySet().stream(), - other.roles.entrySet().stream()) - .collect(Collectors.toMap(Map.Entry::getKey, - Map.Entry::getValue, - (set1, set2) -> Stream.concat(set1.stream(), set2.stream()) - .collect(Collectors.toUnmodifiableSet())))); - } - - /** - * Returns whether any role in this allows action to take place in path - */ - public boolean allows(Action action, URI uri) { - return roles.entrySet().stream().anyMatch(kv -> { - Role role = kv.getKey(); - Set<Context> contexts = kv.getValue(); - return contexts.stream().anyMatch(context -> role.allows(action, uri, context)); - }); - } - - /** - * Returns the set of contexts for which the given role is valid. - */ - public Set<Context> contextsFor(Object role) { // TODO fix. - return roles.getOrDefault((Role) role, Collections.emptySet()); - } - - @Override - public String toString() { - return "roles " + roles; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if ( ! (o instanceof RoleMembership)) return false; - return Objects.equals(roles, ((RoleMembership) o).roles); - } - - @Override - public int hashCode() { - return Objects.hash(roles); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RolePrincipal.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RolePrincipal.java deleted file mode 100644 index afb8a99d94a..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/role/RolePrincipal.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.yahoo.vespa.hosted.controller.role; - -import java.security.Principal; - -/** - * A {@link Principal} with a {@link RoleMembership}. - * - * @author jonmv - */ -public interface RolePrincipal extends Principal { - - /** Returns the roles with context this principal is a member of. */ - RoleMembership roles(); - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java index b0084c38754..e862e12a18b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java @@ -15,7 +15,7 @@ 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.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.Role; import org.junit.Before; import org.junit.Test; 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 4e8ef8128ec..459320741a9 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 @@ -8,9 +8,9 @@ import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper; -import com.yahoo.vespa.hosted.controller.role.Role; -import com.yahoo.vespa.hosted.controller.role.RoleMembership; -import com.yahoo.vespa.hosted.controller.role.RolePrincipal; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.RoleMembership; +import com.yahoo.vespa.hosted.controller.api.role.RolePrincipal; import org.junit.Test; import java.io.IOException; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java deleted file mode 100644 index b4a3e674594..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/PathGroupTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.yahoo.vespa.hosted.controller.role; - -import org.junit.Test; - -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.regex.Pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -/** - * @author jonmv - * @author mpolden - */ -public class PathGroupTest { - - @Test - public void uniqueMatches() { - // Ensure that each path group contains at most one match for any given path, to avoid undefined context extraction. - Set<String> checkedAgainstSelf = new HashSet<>(); - for (PathGroup group1 : PathGroup.values()) - for (PathGroup group2 : PathGroup.values()) - for (String path1 : group1.pathSpecs) - for (String path2 : group2.pathSpecs) { - if (path1.equals(path2)) { - if (checkedAgainstSelf.add(path1)) continue; - fail("Path '" + path1 + "' appears in both '" + group1 + "' and '" + group2 + "'."); - } - - String[] parts1 = path1.split("/"); - String[] parts2 = path2.split("/"); - - int end = Math.min(parts1.length, parts2.length); - // If one path has more parts than the other ... - // and the other doesn't end with a wildcard matcher ... - // and the longest one isn't just one wildcard longer ... - // then one is strictly longer than the other, and it's not a match. - if (end < parts1.length && (end == 0 || ! parts2[end - 1].equals("{*}")) && ! parts1[end].equals("{*}")) continue; - if (end < parts2.length && (end == 0 || ! parts1[end - 1].equals("{*}")) && ! parts2[end].equals("{*}")) continue; - - int i; - for (i = 0; i < end; i++) - if ( ! parts1[i].equals(parts2[i]) - && ! (parts1[i].startsWith("{") && parts1[i].endsWith("}")) - && ! (parts2[i].startsWith("{") && parts2[i].endsWith("}"))) break; - - if (i == end) fail("Paths '" + path1 + "' and '" + path2 + "' overlap."); - } - - assertEquals(PathGroup.all().stream().mapToInt(group -> group.pathSpecs.size()).sum(), - checkedAgainstSelf.size()); - } - - @Test - public void contextMatches() { - for (PathGroup group : PathGroup.values()) - for (String spec : group.pathSpecs) { - for (PathGroup.Matcher matcher : PathGroup.Matcher.values()) { - if (group.matchers.contains(matcher)) { - if ( ! spec.contains(matcher.pattern)) - fail("Spec '" + spec + "' in '" + group.name() + "' should contain matcher '" + matcher.pattern + "'."); - if (spec.replaceFirst(Pattern.quote(matcher.pattern), "").contains(matcher.pattern)) - fail("Spec '" + spec + "' in '" + group.name() + "' contains more than one instance of '" + matcher.pattern + "'."); - } - else if (spec.contains(matcher.pattern)) - fail("Spec '" + spec + "' in '" + group.name() + "' should not contain matcher '" + matcher.pattern + "'."); - } - } - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java deleted file mode 100644 index 26fc095c1d3..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/role/RoleMembershipTest.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.role; - -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; -import org.junit.Test; - -import java.net.URI; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * @author mpolden - */ -public class RoleMembershipTest { - - @Test - public void operator_membership() { - RoleMembership roles = Role.hostedOperator.limitedTo(SystemName.main); - - // Operator actions - assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined"))); - assertTrue(roles.allows(Action.create, URI.create("/controller/v1/foo"))); - assertTrue(roles.allows(Action.update, URI.create("/os/v1/bar"))); - assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); - assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2"))); - } - - @Test - public void tenant_membership() { - RoleMembership roles = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.main); - assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined"))); - assertFalse("Deny access to operator API", roles.allows(Action.create, URI.create("/controller/v1/foo"))); - assertFalse("Deny access to other tenant and app", roles.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2"))); - assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); - - RoleMembership multiContext = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.main) - .and(Role.athenzTenantAdmin.limitedTo(TenantName.from("t2"), SystemName.main)); - assertFalse("Deny access to other tenant and app", multiContext.allows(Action.update, URI.create("/application/v4/tenant/t3/application/a3"))); - assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2"))); - assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); - - RoleMembership publicSystem = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.vaas); - assertFalse(publicSystem.allows(Action.read, URI.create("/controller/v1/foo"))); - assertTrue(publicSystem.allows(Action.read, URI.create("/badge/v1/badge"))); - assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); - } - - @Test - public void build_service_membership() { - RoleMembership roles = Role.tenantPipeline.limitedTo(ApplicationName.from("a1"), TenantName.from("t1"), SystemName.vaas); - assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined"))); - assertFalse(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); - assertTrue(roles.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport"))); - assertFalse("No global read access", roles.allows(Action.read, URI.create("/controller/v1/foo"))); - } - - @Test - public void multi_role_membership() { - RoleMembership roles = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.main) - .and(Role.tenantPipeline.limitedTo(ApplicationName.from("a1"), TenantName.from("t1"), SystemName.main)); - assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined"))); - assertFalse(roles.allows(Action.create, URI.create("/controller/v1/foo"))); - assertTrue(roles.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport"))); - assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); - assertTrue("Global read access", roles.allows(Action.read, URI.create("/controller/v1/foo"))); - assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/"))); - assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/d/nodes"))); - assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/statuspage/v1/incidents"))); - } - -} |