summaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-09-10 11:24:05 +0200
committerMartin Polden <mpolden@mpolden.no>2019-09-10 11:24:26 +0200
commit4b98938fec3e69214c83caccebfe8a2eadae3a1f (patch)
treea4e042e0945eb54e13b1ebeb0286f4be3a8c586f /controller-api
parentfe7475d74b15c32a32d0f3e7be7df6c872e27eb5 (diff)
Move Athenz mocks to controller-api
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java17
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactoryMock.java54
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java93
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java159
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClientMock.java95
5 files changed, 418 insertions, 0 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java
new file mode 100644
index 00000000000..3323cda89b3
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java
@@ -0,0 +1,17 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+
+/**
+ * @author bjorncs
+ */
+public enum ApplicationAction {
+ deploy("deployer"),
+ read("reader"),
+ write("writer");
+
+ public final String roleName;
+
+ ApplicationAction(String roleName) {
+ this.roleName = roleName;
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactoryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactoryMock.java
new file mode 100644
index 00000000000..c228a196228
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactoryMock.java
@@ -0,0 +1,54 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+
+import com.google.inject.Inject;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.client.zms.ZmsClient;
+import com.yahoo.vespa.athenz.client.zts.ZtsClient;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzClientFactoryMock extends AbstractComponent implements AthenzClientFactory {
+
+ private static final Logger log = Logger.getLogger(AthenzClientFactoryMock.class.getName());
+
+ private final AthenzDbMock athenz;
+
+ @Inject
+ public AthenzClientFactoryMock() {
+ this(new AthenzDbMock());
+ }
+
+ public AthenzClientFactoryMock(AthenzDbMock athenz) {
+ this.athenz = athenz;
+ }
+
+ public AthenzDbMock getSetup() {
+ return athenz;
+ }
+
+ @Override
+ public AthenzService getControllerIdentity() {
+ return new AthenzService("vespa.hosting");
+ }
+
+ @Override
+ public ZmsClient createZmsClient() {
+ return new ZmsClientMock(athenz, getControllerIdentity());
+ }
+
+ @Override
+ public ZtsClient createZtsClient() {
+ return new ZtsClientMock(athenz);
+ }
+
+ private static void log(String format, Object... args) {
+ log.log(Level.INFO, String.format(format, args));
+ }
+
+}
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
new file mode 100644
index 00000000000..7ab1ba36aa6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzDbMock.java
@@ -0,0 +1,93 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzDbMock {
+
+ public final Map<AthenzDomain, Domain> domains = new HashMap<>();
+ public final List<AthenzIdentity> hostedOperators = new ArrayList<>();
+
+ public AthenzDbMock addDomain(Domain domain) {
+ domains.put(domain.name, domain);
+ return this;
+ }
+
+ public Domain getOrCreateDomain(AthenzDomain domain) {
+ return domains.computeIfAbsent(domain, Domain::new);
+ }
+
+ public AthenzDbMock addHostedOperator(AthenzIdentity athenzIdentity) {
+ hostedOperators.add(athenzIdentity);
+ return this;
+ }
+
+ public static class Domain {
+
+ public final AthenzDomain name;
+ public final Set<AthenzIdentity> admins = new HashSet<>();
+ public final Set<AthenzIdentity> tenantAdmins = new HashSet<>();
+ public final Map<ApplicationId, Application> applications = new HashMap<>();
+ public final Map<String, Service> services = new HashMap<>();
+ public boolean isVespaTenant = false;
+
+ public Domain(AthenzDomain name) {
+ this.name = name;
+ }
+
+ public Domain admin(AthenzIdentity identity) {
+ admins.add(identity);
+ return this;
+ }
+
+ public Domain tenantAdmin(AthenzIdentity identity) {
+ tenantAdmins.add(identity);
+ return this;
+ }
+
+ /**
+ * Simulates establishing Vespa tenancy in Athens.
+ */
+ public void markAsVespaTenant() {
+ isVespaTenant = true;
+ }
+
+ }
+
+ public static class Application {
+
+ public final Map<ApplicationAction, Set<AthenzIdentity>> acl = new HashMap<>();
+
+ public Application() {
+ acl.put(ApplicationAction.deploy, new HashSet<>());
+ acl.put(ApplicationAction.read, new HashSet<>());
+ acl.put(ApplicationAction.write, new HashSet<>());
+ }
+
+ public Application addRoleMember(ApplicationAction action, AthenzIdentity identity) {
+ acl.get(action).add(identity);
+ return this;
+ }
+ }
+
+ public static class Service {
+
+ public final boolean allowLaunch;
+
+ public Service(boolean allowLaunch) {
+ this.allowLaunch = allowLaunch;
+ }
+ }
+}
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
new file mode 100644
index 00000000000..84aa1033e65
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java
@@ -0,0 +1,159 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzResourceName;
+import com.yahoo.vespa.athenz.api.AthenzRole;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
+import com.yahoo.vespa.athenz.client.zms.RoleAction;
+import com.yahoo.vespa.athenz.client.zms.ZmsClient;
+import com.yahoo.vespa.athenz.client.zms.ZmsClientException;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author bjorncs
+ */
+public class ZmsClientMock implements ZmsClient {
+
+ private static final Logger log = Logger.getLogger(ZmsClientMock.class.getName());
+
+ private final AthenzDbMock athenz;
+ private final AthenzIdentity controllerIdentity;
+ private static final Pattern TENANT_RESOURCE_PATTERN = Pattern.compile("service\\.hosting\\.tenant\\.(?<tenantDomain>[\\w\\-_]+)\\..*");
+ private static final Pattern APPLICATION_RESOURCE_PATTERN = Pattern.compile("service\\.hosting\\.tenant\\.[\\w\\-_]+\\.res_group\\.(?<resourceGroup>[\\w\\-_]+)\\.wildcard");
+
+ public ZmsClientMock(AthenzDbMock athenz, AthenzIdentity controllerIdentity) {
+ this.athenz = athenz;
+ this.controllerIdentity = controllerIdentity;
+ }
+
+ @Override
+ public void createTenancy(AthenzDomain tenantDomain, AthenzIdentity providerService, OktaAccessToken token) {
+ log("createTenancy(tenantDomain='%s')", tenantDomain);
+ getDomainOrThrow(tenantDomain, false).isVespaTenant = true;
+ }
+
+ @Override
+ public void deleteTenancy(AthenzDomain tenantDomain, AthenzIdentity providerService, OktaAccessToken token) {
+ log("deleteTenancy(tenantDomain='%s')", tenantDomain);
+ AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, false);
+ domain.isVespaTenant = false;
+ domain.applications.clear();
+ domain.tenantAdmins.clear();
+ }
+
+ @Override
+ public void createProviderResourceGroup(AthenzDomain tenantDomain, AthenzIdentity providerService, String resourceGroup, Set<RoleAction> roleActions, OktaAccessToken token) {
+ log("createProviderResourceGroup(tenantDomain='%s', resourceGroup='%s')", tenantDomain, resourceGroup);
+ AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, true);
+ ApplicationId applicationId = new ApplicationId(resourceGroup);
+ if (!domain.applications.containsKey(applicationId)) {
+ domain.applications.put(applicationId, new AthenzDbMock.Application());
+ }
+ }
+
+ @Override
+ public void deleteProviderResourceGroup(AthenzDomain tenantDomain, AthenzIdentity providerService, String resourceGroup, OktaAccessToken token) {
+ log("deleteProviderResourceGroup(tenantDomain='%s', resourceGroup='%s')", tenantDomain, resourceGroup);
+ getDomainOrThrow(tenantDomain, true).applications.remove(new ApplicationId(resourceGroup));
+ }
+
+ @Override
+ public boolean getMembership(AthenzRole role, AthenzIdentity identity) {
+ if (role.roleName().equals("admin")) {
+ return getDomainOrThrow(role.domain(), false).admins.contains(identity);
+ }
+ return false;
+ }
+
+ @Override
+ public List<AthenzDomain> getDomainList(String prefix) {
+ log("getDomainList()");
+ return new ArrayList<>(athenz.domains.keySet());
+ }
+
+ @Override
+ public boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity) {
+ log("hasAccess(resource=%s, action=%s, identity=%s)", resource, action, identity);
+ if (resource.getDomain().equals(this.controllerIdentity.getDomain())) {
+ if (isHostedOperator(identity)) {
+ return true;
+ }
+ if (resource.getEntityName().startsWith("service.hosting.tenant.")) {
+ AthenzDomain tenantDomainName = getTenantDomain(resource);
+ AthenzDbMock.Domain tenantDomain = getDomainOrThrow(tenantDomainName, true);
+ if (tenantDomain.admins.contains(identity)) {
+ return true;
+ }
+ if (resource.getEntityName().contains(".res_group.")) {
+ ApplicationId applicationName = new ApplicationId(getResourceGroupName(resource));
+ AthenzDbMock.Application application = tenantDomain.applications.get(applicationName);
+ if (application == null) {
+ throw zmsException(400, "Application '%s' not found", applicationName);
+ }
+ return application.acl.get(ApplicationAction.valueOf(action)).contains(identity);
+ }
+ return false;
+ }
+ return false;
+ } else if ("launch".equals(action)){
+ AthenzDbMock.Domain domain = getDomainOrThrow(resource.getDomain(), false);
+ String serviceName = resource.getEntityName().replace("service.","");
+ if(!domain.services.containsKey(serviceName)) return false;
+ AthenzDbMock.Service service = domain.services.get(serviceName);
+ return service.allowLaunch;
+ }
+ return false;
+ }
+
+ @Override
+ public void close() {}
+
+ private static AthenzDomain getTenantDomain(AthenzResourceName resource) {
+ Matcher matcher = TENANT_RESOURCE_PATTERN.matcher(resource.getEntityName());
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(resource.toResourceNameString());
+ }
+ return new AthenzDomain(matcher.group("tenantDomain"));
+ }
+
+ private static String getResourceGroupName(AthenzResourceName resource) {
+ Matcher matcher = APPLICATION_RESOURCE_PATTERN.matcher(resource.getEntityName());
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(resource.toResourceNameString());
+ }
+ return matcher.group("resourceGroup");
+ }
+
+ private AthenzDbMock.Domain getDomainOrThrow(AthenzDomain domainName, boolean verifyVespaTenant) {
+ AthenzDbMock.Domain domain = Optional.ofNullable(athenz.domains.get(domainName))
+ .orElseThrow(() -> zmsException(400, "Domain '%s' not found", domainName));
+ if (verifyVespaTenant && !domain.isVespaTenant) {
+ throw zmsException(400, "Domain not a Vespa tenant: '%s'", domainName);
+ }
+ return domain;
+ }
+
+ private boolean isHostedOperator(AthenzIdentity identity) {
+ return athenz.hostedOperators.contains(identity);
+ }
+
+ private static ZmsClientException zmsException(int code, String message, Object... args) {
+ return new ZmsClientException(code, String.format(message, args));
+ }
+
+ private static void log(String format, Object... args) {
+ log.log(Level.INFO, String.format(format, args));
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClientMock.java
new file mode 100644
index 00000000000..ec60f11060d
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZtsClientMock.java
@@ -0,0 +1,95 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+
+import com.yahoo.security.Pkcs10Csr;
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzRole;
+import com.yahoo.vespa.athenz.api.AwsRole;
+import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials;
+import com.yahoo.vespa.athenz.api.ZToken;
+import com.yahoo.vespa.athenz.client.zts.Identity;
+import com.yahoo.vespa.athenz.client.zts.InstanceIdentity;
+import com.yahoo.vespa.athenz.client.zts.ZtsClient;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author bjorncs
+ */
+public class ZtsClientMock implements ZtsClient {
+ private static final Logger log = Logger.getLogger(ZtsClientMock.class.getName());
+
+ private final AthenzDbMock athenz;
+
+ public ZtsClientMock(AthenzDbMock athenz) {
+ this.athenz = athenz;
+ }
+
+ @Override
+ public List<AthenzDomain> getTenantDomains(AthenzIdentity providerIdentity, AthenzIdentity userIdentity, String roleName) {
+ log.log(Level.INFO, String.format("getTenantDomains(providerIdentity='%s', userIdentity='%s', roleName='%s')",
+ providerIdentity.getFullName(), userIdentity.getFullName(), roleName));
+ return athenz.domains.values().stream()
+ .filter(domain -> domain.tenantAdmins.contains(userIdentity) || domain.admins.contains(userIdentity))
+ .map(domain -> domain.name)
+ .collect(toList());
+ }
+
+ @Override
+ public InstanceIdentity registerInstance(AthenzIdentity providerIdentity, AthenzIdentity instanceIdentity, String attestationData, Pkcs10Csr csr) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InstanceIdentity refreshInstance(AthenzIdentity providerIdentity, AthenzIdentity instanceIdentity, String instanceId, Pkcs10Csr csr) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Identity getServiceIdentity(AthenzIdentity identity, String keyId, Pkcs10Csr csr) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Identity getServiceIdentity(AthenzIdentity identity, String keyId, KeyPair keyPair, String dnsSuffix) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ZToken getRoleToken(AthenzDomain domain) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ZToken getRoleToken(AthenzRole athenzRole) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public X509Certificate getRoleCertificate(AthenzRole role, Pkcs10Csr csr, Duration expiry) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public X509Certificate getRoleCertificate(AthenzRole role, Pkcs10Csr csr) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole, Duration duration, String externalId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+
+ }
+}