summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2021-10-01 10:32:59 +0200
committerGitHub <noreply@github.com>2021-10-01 10:32:59 +0200
commit856241fd94a505bb35b55c7fa0d41a2ffd86b8af (patch)
tree914973fec2320c8dfce93c22f88581413e31a74d
parent1f2be5c3e6c9e7d0e4b0d8f516109b7108a28458 (diff)
parent2a43b4c0e6ddbf9acd64f1ff07ba5d4d9340c26c (diff)
Merge pull request #19362 from vespa-engine/bjorncs/tenant-operator-role
Bjorncs/tenant operator role
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java76
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java21
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java5
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java8
4 files changed, 78 insertions, 32 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java
index 899e3174df9..a9b20040f20 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java
@@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@@ -43,7 +44,7 @@ public class AthenzDbMock {
public final Map<ApplicationId, Application> applications = new HashMap<>();
public final Map<String, Service> services = new HashMap<>();
public final List<Role> roles = new ArrayList<>();
- public final List<Policy> policies = new ArrayList<>();
+ public final Map<String, Policy> policies = new HashMap<>();
public boolean isVespaTenant = false;
public Domain(AthenzDomain name) {
@@ -52,7 +53,7 @@ public class AthenzDbMock {
public Domain admin(AthenzIdentity identity) {
admins.add(identity);
- policies.add(new Policy("admin", identity.getFullName(), ".*", ".*"));
+ policies.put("admin", new Policy("admin", identity.getFullName(), ".*", ".*"));
return this;
}
@@ -66,8 +67,8 @@ public class AthenzDbMock {
return this;
}
- public Domain withPolicy(String principalRegex, String operation, String resource) {
- policies.add(new Policy("admin", principalRegex, operation, resource));
+ public Domain withPolicy(String name, String principalRegex, String operation, String resource) {
+ policies.put(name, new Policy(name, principalRegex, operation, resource));
return this;
}
@@ -78,6 +79,13 @@ public class AthenzDbMock {
isVespaTenant = true;
}
+ public boolean hasRole(String name) { return roles.stream().anyMatch(r -> r.name.equals(name)); }
+
+ public boolean hasPolicy(String name) { return policies.containsKey(name); }
+
+ public boolean checkAccess(AthenzIdentity principal, String action, String resource) {
+ return policies.values().stream().anyMatch(a -> a.matches(principal, action, resource));
+ }
}
public static class Application {
@@ -107,31 +115,65 @@ public class AthenzDbMock {
public static class Policy {
private final String name;
- private final Pattern principal;
- private final Pattern action;
- private final Pattern resource;
+ final List<Assertion> assertions = new ArrayList<>();
public Policy(String name, String principal, String action, String resource) {
- this.name = name;
- this.principal = Pattern.compile(principal);
- this.action = Pattern.compile(action);
- this.resource = Pattern.compile(resource);
+ this(name);
+ this.assertions.add(new Assertion("grant", principal, action, resource));
}
+ public Policy(String name) { this.name = name; }
+
public String name() {
return name;
}
- public boolean principalMatches(AthenzIdentity athenzIdentity) {
- return this.principal.matcher(athenzIdentity.getFullName()).matches();
+ public boolean matches(String assertion) {
+ return assertions.stream().anyMatch(a -> a.matches(assertion));
+ }
+
+ public boolean matches(AthenzIdentity principal, String action, String resource) {
+ return assertions.stream().anyMatch(a -> a.matches(principal, action, resource));
+ }
+ }
+
+ public static class Assertion {
+ private final String effect;
+ private final String role;
+ private final String action;
+ private final String resource;
+
+ public Assertion(String effect, String role, String action, String resource) {
+ this.effect = effect;
+ this.role = role;
+ this.action = action;
+ this.resource = resource;
+ }
+
+ public Assertion(String role, String action, String resource) { this("grant", role, action, resource); }
+
+ public boolean matches(AthenzIdentity principal, String action, String resource) {
+ return Pattern.compile(this.role).matcher(principal.getFullName()).matches()
+ && Pattern.compile(this.action).matcher(action).matches()
+ && Pattern.compile(this.resource).matcher(resource).matches();
}
- public boolean actionMatches(String operation) {
- return this.action.matcher(operation).matches();
+ public boolean matches(String assertion) { return asString().equals(assertion); }
+
+ public String asString() { return String.format("%s %s to %s on %s", effect, action, role, resource).toLowerCase(); }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Assertion assertion = (Assertion) o;
+ return Objects.equals(effect, assertion.effect) && Objects.equals(role, assertion.role)
+ && Objects.equals(action, assertion.action) && Objects.equals(resource, assertion.resource);
}
- public boolean resourceMatches(String resource) {
- return this.resource.matcher(resource).matches();
+ @Override
+ public int hashCode() {
+ return Objects.hash(effect, role, action, resource);
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
index 42a5e2b42be..b362a0c7a47 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
@@ -158,27 +158,24 @@ public class ZmsClientMock implements ZmsClient {
return false;
} else {
AthenzDbMock.Domain domain = getDomainOrThrow(resource.getDomain(), false);
- return domain.policies.stream()
- .anyMatch(policy ->
- policy.principalMatches(identity) &&
- policy.actionMatches(action) &&
- policy.resourceMatches(resource.getEntityName()));
+ return domain.checkAccess(identity, action, resource.getEntityName());
}
}
@Override
public void createPolicy(AthenzDomain athenzDomain, String athenzPolicy) {
- List<AthenzDbMock.Policy> policies = athenz.getOrCreateDomain(athenzDomain).policies;
- if (policies.stream().anyMatch(p -> p.name().equals(athenzPolicy))) {
+ Map<String, AthenzDbMock.Policy> policies = athenz.getOrCreateDomain(athenzDomain).policies;
+ if (policies.containsKey(athenzPolicy)) {
throw new IllegalArgumentException("Policy already exists");
}
-
- // Policy will be created in the mock when an assertion is added
+ policies.put(athenzPolicy, new AthenzDbMock.Policy(athenzPolicy));
}
@Override
public void addPolicyRule(AthenzDomain athenzDomain, String athenzPolicy, String action, AthenzResourceName resourceName, AthenzRole athenzRole) {
- athenz.getOrCreateDomain(athenzDomain).policies.add(new AthenzDbMock.Policy(athenzPolicy, athenzRole.roleName(), action, resourceName.toResourceNameString()));
+ AthenzDbMock.Policy policy = athenz.getOrCreateDomain(athenzDomain).policies.get(athenzPolicy);
+ if (policy == null) throw new IllegalArgumentException("No policy with name " + athenzPolicy);
+ policy.assertions.add(new AthenzDbMock.Assertion(athenzRole.roleName(), action, resourceName.toResourceNameString()));
}
@Override
@@ -235,9 +232,7 @@ public class ZmsClientMock implements ZmsClient {
@Override
public Set<String> listPolicies(AthenzDomain domain) {
- return athenz.getOrCreateDomain(domain).policies.stream()
- .map(AthenzDbMock.Policy::name)
- .collect(Collectors.toSet());
+ return athenz.getOrCreateDomain(domain).policies.keySet();
}
@Override
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 596a0b186db..d2eb43d31f8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -1479,7 +1479,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Allow developer launch privilege to domain1.service. Deployment now completes.
AthenzDbMock.Domain domainMock = tester.athenzClientFactory().getSetup().getOrCreateDomain(ATHENZ_TENANT_DOMAIN);
- domainMock.withPolicy("user." + developer.id(), "launch", "service.service");
+ domainMock.withPolicy("launch-" +developer.id(), "user." + developer.id(), "launch", "service.service");
tester.assertResponse(request("/application/v4/tenant/sandbox/application/myapp/instance/default/deploy/dev-us-east-1", POST)
@@ -1757,7 +1757,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
*/
private void allowLaunchOfService(com.yahoo.vespa.athenz.api.AthenzService service) {
AthenzDbMock.Domain domainMock = tester.athenzClientFactory().getSetup().getOrCreateDomain(service.getDomain());
- domainMock.withPolicy(tester.controller().zoneRegistry().accessControlDomain().value()+".provider.*","launch", "service." + service.getName());
+ String principalRegex = tester.controller().zoneRegistry().accessControlDomain().value() + ".provider.*";
+ domainMock.withPolicy("provider-launch", principalRegex,"launch", "service." + service.getName());
}
/**
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index d35164afad7..42ac04b546b 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -298,6 +298,14 @@ public class Flags {
HOSTNAME
);
+ public static final UnboundBooleanFlag ENABLE_TENANT_OPERATOR_ROLE = defineFeatureFlag(
+ "enable-tenant-operator-role", false,
+ List.of("bjorncs"), "2021-09-29", "2021-12-31",
+ "Enable tenant specific operator roles in public systems. For controllers only.",
+ "Takes effect on subsequent maintainer invocation",
+ TENANT_ID
+ );
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners,
String createdAt, String expiresAt, String description,