diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-09-10 11:24:05 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-09-10 11:24:26 +0200 |
commit | 4b98938fec3e69214c83caccebfe8a2eadae3a1f (patch) | |
tree | a4e042e0945eb54e13b1ebeb0286f4be3a8c586f /controller-api | |
parent | fe7475d74b15c32a32d0f3e7be7df6c872e27eb5 (diff) |
Move Athenz mocks to controller-api
Diffstat (limited to 'controller-api')
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() { + + } +} |