summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java34
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java24
-rw-r--r--controller-server/pom.xml44
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java24
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java36
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java (renamed from controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java)2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java (renamed from controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/HostedAthenzIdentities.java)2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java104
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java64
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientFacade.java122
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java191
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java117
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java37
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java25
-rw-r--r--controller-server/src/main/resources/configdefinitions/athenz.def29
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java25
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java74
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java4
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzResourceGroup.java40
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/OktaAccessToken.java42
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java95
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/bindings/ErrorResponseEntity.java (renamed from vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/ErrorResponseEntity.java)2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/Pkcs10CsrSerializer.java (renamed from vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/Pkcs10CsrSerializer.java)2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/X509CertificateDeserializer.java (renamed from vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateDeserializer.java)2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/X509CertificateListDeserializer.java (renamed from vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateListDeserializer.java)2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/package-info.java (renamed from vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/package-info.java)2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java141
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/RoleAction.java49
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java35
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClientException.java32
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/AccessResponseEntity.java19
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/DomainListResponseEntity.java21
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipResponseEntity.java28
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ProviderResourceGroupRolesRequestEntity.java56
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/TenancyRequestEntity.java31
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java79
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java4
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java2
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java2
53 files changed, 952 insertions, 784 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java
index e95e97527da..aba3b5f3ab7 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzClientFactory.java
@@ -1,8 +1,8 @@
// 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.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.client.zms.ZmsClient;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
/**
@@ -10,12 +10,9 @@ import com.yahoo.vespa.athenz.client.zts.ZtsClient;
*/
public interface AthenzClientFactory {
- AthenzIdentity getControllerIdentity();
+ AthenzService getControllerIdentity();
- ZmsClient createZmsClientWithServicePrincipal();
-
- ZtsClient createZtsClientWithServicePrincipal();
-
- ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken);
+ ZmsClient createZmsClient();
+ ZtsClient createZtsClient();
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java
deleted file mode 100644
index 3630748b10a..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClient.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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.List;
-
-/**
- * @author bjorncs
- */
-public interface ZmsClient {
-
- void createTenant(AthenzDomain tenantDomain);
-
- void deleteTenant(AthenzDomain tenantDomain);
-
- void addApplication(AthenzDomain tenantDomain, ApplicationId applicationName);
-
- void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName);
-
- boolean hasApplicationAccess(AthenzIdentity athenzIdentity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName);
-
- boolean hasTenantAdminAccess(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain);
-
- boolean hasHostedOperatorAccess(AthenzIdentity identity);
-
- // Used before vespa tenancy is established for the domain.
- boolean isDomainAdmin(AthenzIdentity athenzIdentity, AthenzDomain domain);
-
- List<AthenzDomain> getDomainList(String prefix);
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java
deleted file mode 100644
index 31e9e549c08..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsException.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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 class ZmsException extends RuntimeException {
-
- private final int code;
-
- public ZmsException(int code, Throwable cause) {
- super(cause.getMessage(), cause);
- this.code = code;
- }
-
- public ZmsException(int code, String message) {
- super(message);
- this.code = code;
- }
-
- public int getCode() {
- return code;
- }
-}
diff --git a/controller-server/pom.xml b/controller-server/pom.xml
index 44954e19fea..b15a561c1d2 100644
--- a/controller-server/pom.xml
+++ b/controller-server/pom.xml
@@ -130,50 +130,6 @@
<version>1.6</version>
</dependency>
- <dependency>
- <groupId>com.yahoo.athenz</groupId>
- <artifactId>athenz-zms-java-client</artifactId>
- <scope>compile</scope>
- <exclusions>
- <exclusion>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </exclusion>
- <!-- Exclude all Jersey bundles provided by JDisc -->
- <exclusion>
- <groupId>org.glassfish.jersey.core</groupId>
- <artifactId>jersey-client</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.glassfish.jersey.media</groupId>
- <artifactId>jersey-media-json-jackson</artifactId>
- </exclusion>
- <!-- BouncyCastle is not bundled due to class loading issues
- when security provider is registered from inside a OSGi bundle -->
- <exclusion>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcpkix-jdk15on</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
- </exclusion>
- <!--Exclude all Jackson bundles provided by JDisc -->
- <exclusion>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- </exclusion>
- <exclusion>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- </exclusion>
- <exclusion>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-annotations</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
<!-- test -->
<dependency>
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 21afe1b0778..bc48051c111 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -9,7 +9,7 @@ import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
@@ -20,7 +20,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.Hostname;
import com.yahoo.vespa.hosted.controller.api.identifiers.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.BuildService;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log;
@@ -44,6 +43,7 @@ import com.yahoo.vespa.hosted.controller.application.JobList;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.JobStatus.JobRun;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
+import com.yahoo.vespa.hosted.controller.athenz.impl.ZmsClientFacade;
import com.yahoo.vespa.hosted.controller.concurrent.Once;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
@@ -94,7 +94,7 @@ public class ApplicationController {
private final ArtifactRepository artifactRepository;
private final ApplicationStore applicationStore;
private final RotationRepository rotationRepository;
- private final AthenzClientFactory zmsClientFactory;
+ private final ZmsClientFacade zmsClient;
private final NameService nameService;
private final ConfigServer configServer;
private final RoutingGenerator routingGenerator;
@@ -109,7 +109,7 @@ public class ApplicationController {
RoutingGenerator routingGenerator, BuildService buildService, Clock clock) {
this.controller = controller;
this.curator = curator;
- this.zmsClientFactory = zmsClientFactory;
+ this.zmsClient = new ZmsClientFacade(zmsClientFactory.createZmsClient(), zmsClientFactory.getControllerIdentity());
this.nameService = nameService;
this.configServer = configServer;
this.routingGenerator = routingGenerator;
@@ -251,7 +251,7 @@ public class ApplicationController {
*
* @throws IllegalArgumentException if the application already exists
*/
- public Application createApplication(ApplicationId id, Optional<NToken> token) {
+ public Application createApplication(ApplicationId id, Optional<OktaAccessToken> token) {
if ( ! (id.instance().isDefault())) // TODO: Support instances properly
throw new IllegalArgumentException("Only the instance name 'default' is supported at the moment");
if (id.instance().isTester())
@@ -270,11 +270,10 @@ public class ApplicationController {
throw new IllegalArgumentException("Could not create '" + id + "': Application " + dashToUnderscore(id) + " already exists");
if (id.instance().isDefault() && tenant.get() instanceof AthenzTenant) { // Only create the athenz application for "default" instances.
if ( ! token.isPresent())
- throw new IllegalArgumentException("Could not create '" + id + "': No NToken provided");
+ throw new IllegalArgumentException("Could not create '" + id + "': No Okta Access Token provided");
- ZmsClient zmsClient = zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get());
zmsClient.addApplication(((AthenzTenant) tenant.get()).domain(),
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()));
+ new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()), token.get());
}
LockedApplication application = new LockedApplication(new Application(id, clock.instant()), lock);
store(application);
@@ -532,7 +531,7 @@ public class ApplicationController {
* @throws IllegalArgumentException if the application has deployments or the caller is not authorized
* @throws NotExistsException if no instances of the application exist
*/
- public void deleteApplication(ApplicationId applicationId, Optional<NToken> token) {
+ public void deleteApplication(ApplicationId applicationId, Optional<OktaAccessToken> token) {
// Find all instances of the application
List<ApplicationId> instances = asList(applicationId.tenant()).stream()
.map(Application::id)
@@ -549,13 +548,12 @@ public class ApplicationController {
Tenant tenant = controller.tenants().tenant(id.tenant()).get();
if (tenant instanceof AthenzTenant && ! token.isPresent())
- throw new IllegalArgumentException("Could not delete '" + application + "': No NToken provided");
+ throw new IllegalArgumentException("Could not delete '" + application + "': No Okta Access Token provided");
// Only delete in Athenz once
if (id.instance().isDefault() && tenant instanceof AthenzTenant) {
- zmsClientFactory.createZmsClientWithAuthorizedServiceToken(token.get())
- .deleteApplication(((AthenzTenant) tenant).domain(),
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()));
+ zmsClient.deleteApplication(((AthenzTenant) tenant).domain(),
+ new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value()), token.get());
}
curator.removeApplication(id);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
index 9e3c5a2d683..47e11af056d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java
@@ -26,6 +26,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.github.GitHub;
import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.controller.api.integration.zone.CloudName;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
+import com.yahoo.vespa.hosted.controller.athenz.impl.ZmsClientFacade;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.versions.OsVersion;
@@ -74,7 +75,7 @@ public class Controller extends AbstractComponent {
private final ConfigServer configServer;
private final MetricsService metricsService;
private final Chef chef;
- private final AthenzClientFactory athenzClientFactory;
+ private final ZmsClientFacade zmsClient;
/**
* Creates a controller
@@ -114,7 +115,7 @@ public class Controller extends AbstractComponent {
this.metricsService = Objects.requireNonNull(metricsService, "MetricsService cannot be null");
this.chef = Objects.requireNonNull(chef, "Chef cannot be null");
this.clock = Objects.requireNonNull(clock, "Clock cannot be null");
- this.athenzClientFactory = Objects.requireNonNull(athenzClientFactory, "AthenzClientFactory cannot be null");
+ this.zmsClient = new ZmsClientFacade(athenzClientFactory.createZmsClient(), athenzClientFactory.getControllerIdentity());
jobController = new JobController(this, runDataStore, Objects.requireNonNull(testerCloud));
applicationController = new ApplicationController(this, curator, athenzClientFactory,
@@ -144,7 +145,7 @@ public class Controller extends AbstractComponent {
public JobController jobController() { return jobController; }
public List<AthenzDomain> getDomainList(String prefix) {
- return athenzClientFactory.createZmsClientWithServicePrincipal().getDomainList(prefix);
+ return zmsClient.getDomainList(prefix);
}
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index 1ae3e6a6577..f0e13349fbf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -3,13 +3,14 @@ package com.yahoo.vespa.hosted.controller;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
+import com.yahoo.vespa.hosted.controller.athenz.impl.ZmsClientFacade;
import com.yahoo.vespa.hosted.controller.concurrent.Once;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
@@ -41,12 +42,16 @@ public class TenantController {
private final Controller controller;
private final CuratorDb curator;
- private final AthenzClientFactory athenzClientFactory;
+ private final ZmsClientFacade zmsClient;
+ private final ZtsClient ztsClient;
+ private final AthenzService controllerIdentity;
public TenantController(Controller controller, CuratorDb curator, AthenzClientFactory athenzClientFactory) {
this.controller = Objects.requireNonNull(controller, "controller must be non-null");
this.curator = Objects.requireNonNull(curator, "curator must be non-null");
- this.athenzClientFactory = Objects.requireNonNull(athenzClientFactory, "athenzClientFactory must be non-null");
+ this.controllerIdentity = athenzClientFactory.getControllerIdentity();
+ this.zmsClient = new ZmsClientFacade(athenzClientFactory.createZmsClient(), controllerIdentity);
+ this.ztsClient = athenzClientFactory.createZtsClient();
// Update serialization format of all tenants
Once.after(Duration.ofMinutes(1), () -> {
@@ -84,13 +89,11 @@ public class TenantController {
/** Returns a list of all tenants accessible by the given user */
public List<Tenant> asList(UserId user) {
AthenzUser athenzUser = AthenzUser.fromUserId(user.id());
- try (ZtsClient ztsClient = athenzClientFactory.createZtsClientWithServicePrincipal()) {
- Set<AthenzDomain> userDomains = new HashSet<>(ztsClient.getTenantDomains(athenzClientFactory.getControllerIdentity(), athenzUser, "admin"));
+ Set<AthenzDomain> userDomains = new HashSet<>(ztsClient.getTenantDomains(controllerIdentity, athenzUser, "admin"));
return asList().stream()
.filter(tenant -> isUser(tenant, user) ||
userDomains.stream().anyMatch(domain -> inDomain(tenant, domain)))
.collect(Collectors.toList());
- }
}
/**
@@ -124,7 +127,7 @@ public class TenantController {
}
/** Create an Athenz tenant */
- public void create(AthenzTenant tenant, NToken token) {
+ public void create(AthenzTenant tenant, OktaAccessToken token) {
try (Lock lock = lock(tenant.name())) {
requireNonExistent(tenant.name());
AthenzDomain domain = tenant.domain();
@@ -136,7 +139,7 @@ public class TenantController {
existingTenantWithDomain.get().name().value() +
"'");
}
- athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token).createTenant(domain);
+ zmsClient.createTenant(domain, token);
curator.writeTenant(tenant);
}
}
@@ -169,7 +172,7 @@ public class TenantController {
}
/** Update Athenz domain for tenant. Returns the updated tenant which must be explicitly stored */
- public LockedTenant withDomain(LockedTenant tenant, AthenzDomain newDomain, NToken token) {
+ public LockedTenant withDomain(LockedTenant tenant, AthenzDomain newDomain, OktaAccessToken token) {
AthenzDomain existingDomain = tenant.get().domain();
if (existingDomain.equals(newDomain)) return tenant;
Optional<Tenant> existingTenantWithNewDomain = tenantIn(newDomain);
@@ -177,12 +180,11 @@ public class TenantController {
throw new IllegalArgumentException("Could not set domain of " + tenant + " to '" + newDomain +
"':" + existingTenantWithNewDomain.get() + " already has this domain");
- ZmsClient zmsClient = athenzClientFactory.createZmsClientWithAuthorizedServiceToken(token);
- zmsClient.createTenant(newDomain);
+ zmsClient.createTenant(newDomain, token);
List<Application> applications = controller.applications().asList(tenant.get().name());
- applications.forEach(a -> zmsClient.addApplication(newDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value())));
- applications.forEach(a -> zmsClient.deleteApplication(existingDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value())));
- zmsClient.deleteTenant(existingDomain);
+ applications.forEach(a -> zmsClient.addApplication(newDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value()), token));
+ applications.forEach(a -> zmsClient.deleteApplication(existingDomain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(a.id().application().value()), token));
+ zmsClient.deleteTenant(existingDomain, token);
log.info("Set Athenz domain for '" + tenant + "' from '" + existingDomain + "' to '" + newDomain + "'");
return tenant.with(newDomain);
@@ -196,10 +198,10 @@ public class TenantController {
}
/** Delete an Athenz tenant */
- public void deleteTenant(AthenzTenant tenant, NToken nToken) {
+ public void deleteTenant(AthenzTenant tenant, OktaAccessToken token) {
try (Lock lock = lock(tenant.name())) {
deleteTenant(tenant.name());
- athenzClientFactory.createZmsClientWithAuthorizedServiceToken(nToken).deleteTenant(tenant.domain());
+ zmsClient.deleteTenant(tenant.domain(), token);
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java
index 3323cda89b3..8614414dc95 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ApplicationAction.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ApplicationAction.java
@@ -1,5 +1,5 @@
// 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;
+package com.yahoo.vespa.hosted.controller.athenz;
/**
* @author bjorncs
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/HostedAthenzIdentities.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java
index bd385034a90..08dbdff13db 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/HostedAthenzIdentities.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java
@@ -1,5 +1,5 @@
// Copyright 2018 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;
+package com.yahoo.vespa.hosted.controller.athenz;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzService;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
deleted file mode 100644
index 26cd9f2e9b8..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// 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.athenz.filter;
-
-import com.google.inject.Inject;
-import com.yahoo.jdisc.Response;
-import com.yahoo.jdisc.http.filter.DiscFilterRequest;
-import com.yahoo.jdisc.http.filter.security.athenz.AthenzPrincipalFilter;
-import com.yahoo.jdisc.http.filter.security.athenz.AthenzPrincipalFilterConfig;
-import com.yahoo.jdisc.http.filter.security.cors.CorsFilterConfig;
-import com.yahoo.log.LogLevel;
-import com.yahoo.vespa.athenz.api.AthenzPrincipal;
-import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.api.NToken;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
-import com.yahoo.yolean.chain.After;
-
-import java.security.Principal;
-import java.util.Optional;
-import java.util.logging.Logger;
-import java.util.stream.Stream;
-
-
-/**
- * A variant of the {@link AthenzPrincipalFilter} to be used in combination with a cookie-based
- * security filter for user authentication
- * Assumes that the user authentication filter configured in the same filter chain and is configured to run before this filter.
- *
- * @author bjorncs
- */
-// TODO Remove this filter once migrated to Okta
-@After({"CorsPreflightRequestFilter", "BouncerFilter"})
-public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter {
-
- private static final Logger log = Logger.getLogger(UserAuthWithAthenzPrincipalFilter.class.getName());
-
- private final String userAuthenticationPassThruAttribute;
- private final String principalHeaderName;
-
- @Inject
- public UserAuthWithAthenzPrincipalFilter(AthenzPrincipalFilterConfig filterConfig, AthenzConfig athenzConfig, CorsFilterConfig corsConfig) {
- super(filterConfig, corsConfig);
- this.userAuthenticationPassThruAttribute = athenzConfig.userAuthenticationPassThruAttribute();
- this.principalHeaderName = filterConfig.principalHeaderName();
- }
-
- @Override
- public Optional<ErrorResponse> filterRequest(DiscFilterRequest request) {
- if (request.getMethod().equals("OPTIONS")) return Optional.empty(); // Skip authentication on OPTIONS - required for Javascript CORS
-
- try {
- switch (getUserAuthenticationResult(request)) {
- case USER_COOKIE_MISSING:
- case USER_COOKIE_ALTERNATIVE_MISSING:
- return super.filterRequest(request); // Cookie-based authentication failed, delegate to Athenz
- case USER_COOKIE_OK:
- rewriteUserPrincipalToAthenz(request);
- return Optional.empty(); // Authenticated using user cookie
- case USER_COOKIE_INVALID:
- return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Your user cookie is invalid (either expired, tampered or invalid ip)"));
- default:
- return Optional.empty();
- }
- } catch (Exception e) {
- log.log(LogLevel.WARNING, "Authentication failed: " + e.getMessage(), e);
- return Optional.of(new ErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage()));
- }
- }
-
- private UserAuthenticationResult getUserAuthenticationResult(DiscFilterRequest request) {
- if (!request.containsAttribute(userAuthenticationPassThruAttribute)) {
- throw new IllegalStateException("User authentication filter passthru attribute missing");
- }
- Integer statusCode = (Integer) request.getAttribute(userAuthenticationPassThruAttribute);
- return Stream.of(UserAuthenticationResult.values())
- .filter(uar -> uar.statusCode == statusCode)
- .findAny()
- .orElseThrow(() -> new IllegalStateException("Invalid status code: " + statusCode));
- }
-
- private void rewriteUserPrincipalToAthenz(DiscFilterRequest request) {
- Principal userPrincipal = request.getUserPrincipal();
- log.log(LogLevel.DEBUG, () -> "Original user principal: " + userPrincipal.toString());
- UserId userId = new UserId(userPrincipal.getName());
- AthenzUser athenzIdentity = AthenzUser.fromUserId(userId.id());
- request.setRemoteUser(athenzIdentity.getFullName());
- NToken nToken = Optional.ofNullable(request.getHeader(principalHeaderName)).map(NToken::new).orElse(null);
- request.setUserPrincipal(new AthenzPrincipal(athenzIdentity, nToken));
- }
-
- private enum UserAuthenticationResult {
- USER_COOKIE_MISSING(0),
- USER_COOKIE_OK(1),
- USER_COOKIE_INVALID(-1),
- USER_COOKIE_ALTERNATIVE_MISSING(-2);
-
- final int statusCode;
-
- UserAuthenticationResult(int statusCode) {
- this.statusCode = statusCode;
- }
-
- }
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
index 633c0470080..846c90a96f5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
@@ -2,47 +2,33 @@
package com.yahoo.vespa.hosted.controller.athenz.impl;
import com.google.inject.Inject;
-import com.yahoo.athenz.auth.Principal;
-import com.yahoo.athenz.auth.impl.PrincipalAuthority;
-import com.yahoo.athenz.auth.impl.SimplePrincipal;
-import com.yahoo.athenz.auth.token.PrincipalToken;
-import com.yahoo.athenz.auth.util.Crypto;
-import com.yahoo.athenz.zms.ZMSClient;
-import com.yahoo.container.jdisc.secretstore.SecretStore;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.client.zms.DefaultZmsClient;
+import com.yahoo.vespa.athenz.client.zms.ZmsClient;
import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
-import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
import java.net.URI;
-import java.security.PrivateKey;
/**
* @author bjorncs
*/
public class AthenzClientFactoryImpl implements AthenzClientFactory {
- private final SecretStore secretStore;
private final AthenzConfig config;
- private final AthenzPrincipalAuthority athenzPrincipalAuthority;
private final ServiceIdentityProvider identityProvider;
@Inject
- public AthenzClientFactoryImpl(SecretStore secretStore, ServiceIdentityProvider identityProvider, AthenzConfig config) {
- this.secretStore = secretStore;
+ public AthenzClientFactoryImpl(ServiceIdentityProvider identityProvider, AthenzConfig config) {
this.identityProvider = identityProvider;
this.config = config;
- this.athenzPrincipalAuthority = new AthenzPrincipalAuthority(config.principalHeaderName());
}
@Override
- public AthenzIdentity getControllerIdentity() {
+ public AthenzService getControllerIdentity() {
return identityProvider.identity();
}
@@ -50,52 +36,16 @@ public class AthenzClientFactoryImpl implements AthenzClientFactory {
* @return A ZMS client instance with the service identity as principal.
*/
@Override
- public ZmsClient createZmsClientWithServicePrincipal() {
- return new ZmsClientImpl(new ZMSClient(config.zmsUrl(), identityProvider.getIdentitySslContext()), config);
+ public ZmsClient createZmsClient() {
+ return new DefaultZmsClient(URI.create(config.zmsUrl()), identityProvider);
}
/**
* @return A ZTS client instance with the service identity as principal.
*/
@Override
- public ZtsClient createZtsClientWithServicePrincipal() {
+ public ZtsClient createZtsClient() {
return new DefaultZtsClient(URI.create(config.ztsUrl()), identityProvider);
}
- /**
- * @return A ZMS client created with a dual principal representing both the tenant admin and the service identity.
- */
- @Override
- public ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken) {
- PrincipalToken signedToken = new PrincipalToken(authorizedServiceToken.getRawToken());
- AthenzConfig.Service service = config.service();
- signedToken.signForAuthorizedService(
- config.domain() + "." + service.name(), service.publicKeyId(), getServicePrivateKey());
-
- Principal dualPrincipal = SimplePrincipal.create(
- AthenzIdentities.USER_PRINCIPAL_DOMAIN.getName(), signedToken.getName(), signedToken.getSignedToken(), athenzPrincipalAuthority);
- return new ZmsClientImpl(new ZMSClient(config.legacyZmsUrl(), dualPrincipal), config);
-
- }
-
- private PrivateKey getServicePrivateKey() {
- AthenzConfig.Service service = config.service();
- String privateKey = secretStore.getSecret(service.privateKeySecretName(), service.privateKeyVersion()).trim();
- return Crypto.loadPrivateKey(privateKey);
- }
-
- private static class AthenzPrincipalAuthority extends PrincipalAuthority {
- private final String principalHeaderName;
-
- public AthenzPrincipalAuthority(String principalHeaderName) {
- this.principalHeaderName = principalHeaderName;
- }
-
- @Override
- public String getHeader() {
- return principalHeaderName;
- }
- }
-
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientFacade.java
new file mode 100644
index 00000000000..09619a33cc4
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientFacade.java
@@ -0,0 +1,122 @@
+// 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.athenz.impl;
+
+import com.yahoo.log.LogLevel;
+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.AthenzService;
+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.hosted.controller.api.identifiers.ApplicationId;
+import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * @author bjorncs
+ */
+public class ZmsClientFacade {
+
+ private static final Logger log = Logger.getLogger(ZmsClientFacade.class.getName());
+ private final ZmsClient zmsClient;
+ private final AthenzService service;
+
+ public ZmsClientFacade(ZmsClient zmsClient, AthenzService identity) {
+ this.zmsClient = zmsClient;
+ this.service = identity;
+ }
+
+ public void createTenant(AthenzDomain tenantDomain, OktaAccessToken token) {
+ log("createTenancy(tenantDomain=%s, service=%s)", tenantDomain, service);
+ zmsClient.createTenancy(tenantDomain, service, token);
+ }
+
+ public void deleteTenant(AthenzDomain tenantDomain, OktaAccessToken token) {
+ log("deleteTenancy(tenantDomain=%s, service=%s)", tenantDomain, service);
+ zmsClient.deleteTenancy(tenantDomain, service, token);
+ }
+
+ public void addApplication(AthenzDomain tenantDomain, ApplicationId applicationName, OktaAccessToken token) {
+ Set<RoleAction> tenantRoleActions = createTenantRoleActions();
+ log("createProviderResourceGroup(" +
+ "tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)",
+ tenantDomain, service.getDomain().getName(), service.getName(), applicationName, tenantRoleActions);
+ zmsClient.createProviderResourceGroup(tenantDomain, service, applicationName.id(), tenantRoleActions, token);
+ }
+
+ public void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName, OktaAccessToken token) {
+ log("deleteProviderResourceGroup(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)",
+ tenantDomain, service.getDomain().getName(), service.getName(), applicationName);
+ zmsClient.deleteProviderResourceGroup(tenantDomain, service, applicationName.id(), token);
+ }
+
+ public boolean hasApplicationAccess(
+ AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
+ return hasAccess(
+ action.name(), applicationResourceString(tenantDomain, applicationName), identity);
+ }
+
+ public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) {
+ return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), identity);
+ }
+
+ public boolean hasHostedOperatorAccess(AthenzIdentity identity) {
+ return hasAccess("modify", service.getDomain().getName() + ":hosted-vespa", identity);
+ }
+
+ /**
+ * Used when creating tenancies. As there are no tenancy policies at this point,
+ * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)}
+ */
+ public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) {
+ log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", identity);
+ return zmsClient.getMembership(new AthenzRole(domain, "admin"), identity);
+ }
+
+ public List<AthenzDomain> getDomainList(String prefix) {
+ log.log(LogLevel.DEBUG, String.format("getDomainList(prefix=%s)", prefix));
+ return zmsClient.getDomainList(prefix);
+ }
+
+ private static Set<RoleAction> createTenantRoleActions() {
+ return Arrays.stream(ApplicationAction.values())
+ .map(action -> new RoleAction(action.roleName, action.name()))
+ .collect(Collectors.toSet());
+ }
+
+ private boolean hasAccess(String action, String resource, AthenzIdentity identity) {
+ log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, identity);
+ return zmsClient.hasAccess(AthenzResourceName.fromString(resource), action, identity);
+ }
+
+ private static void log(String format, Object... args) {
+ log.log(LogLevel.DEBUG, String.format(format, args));
+ }
+
+ private String resourceStringPrefix(AthenzDomain tenantDomain) {
+ return String.format("%s:service.%s.tenant.%s",
+ service.getDomain().getName(), service.getName(), tenantDomain.getName());
+ }
+
+ private String tenantResourceString(AthenzDomain tenantDomain) {
+ return resourceStringPrefix(tenantDomain) + ".wildcard";
+ }
+
+ private String applicationResourceString(AthenzDomain tenantDomain, ApplicationId applicationName) {
+ return resourceStringPrefix(tenantDomain) + "." + "res_group" + "." + applicationName.id() + ".wildcard";
+ }
+
+ private enum TenantAction {
+ // This is meant to match only the '*' action of the 'admin' role.
+ // If needed, we can replace it with 'create', 'delete' etc. later.
+ _modify_
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java
deleted file mode 100644
index 6179d9891fd..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java
+++ /dev/null
@@ -1,191 +0,0 @@
-// 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.athenz.impl;
-
-import com.yahoo.athenz.zms.DomainList;
-import com.yahoo.athenz.zms.ProviderResourceGroupRoles;
-import com.yahoo.athenz.zms.Tenancy;
-import com.yahoo.athenz.zms.TenantRoleAction;
-import com.yahoo.athenz.zms.ZMSClient;
-import com.yahoo.athenz.zms.ZMSClientException;
-import com.yahoo.log.LogLevel;
-import com.yahoo.vespa.athenz.api.AthenzDomain;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.AthenzService;
-import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
-import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Supplier;
-import java.util.logging.Logger;
-
-import static java.util.stream.Collectors.toList;
-
-/**
- * @author bjorncs
- */
-public class ZmsClientImpl implements ZmsClient {
-
- private static final Logger log = Logger.getLogger(ZmsClientImpl.class.getName());
- private final ZMSClient zmsClient;
- private final AthenzService service;
-
- ZmsClientImpl(ZMSClient zmsClient, AthenzConfig config) {
- this.zmsClient = zmsClient;
- this.service = new AthenzService(config.domain(), config.service().name());
- }
-
- @Override
- public void createTenant(AthenzDomain tenantDomain) {
- log("putTenancy(tenantDomain=%s, service=%s)", tenantDomain, service);
- runOrThrow(() -> {
- Tenancy tenancy = new Tenancy()
- .setDomain(tenantDomain.getName())
- .setService(service.getFullName())
- .setResourceGroups(Collections.emptyList());
- zmsClient.putTenancy(tenantDomain.getName(), service.getFullName(), /*auditref*/null, tenancy);
- });
- }
-
- @Override
- public void deleteTenant(AthenzDomain tenantDomain) {
- log("deleteTenancy(tenantDomain=%s, service=%s)", tenantDomain, service);
- runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.getName(), service.getFullName(), /*auditref*/null));
- }
-
- @Override
- public void addApplication(AthenzDomain tenantDomain, ApplicationId applicationName) {
- List<TenantRoleAction> tenantRoleActions = createTenantRoleActions();
- log("putProviderResourceGroupRoles(" +
- "tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)",
- tenantDomain, service.getDomain().getName(), service.getName(), applicationName, tenantRoleActions);
- runOrThrow(() -> {
- ProviderResourceGroupRoles resourceGroupRoles = new ProviderResourceGroupRoles()
- .setDomain(service.getDomain().getName())
- .setService(service.getName())
- .setTenant(tenantDomain.getName())
- .setResourceGroup(applicationName.id())
- .setRoles(tenantRoleActions);
- zmsClient.putProviderResourceGroupRoles(
- tenantDomain.getName(), service.getDomain().getName(), service.getName(),
- applicationName.id(), /*auditref*/null, resourceGroupRoles);
- });
- }
-
- @Override
- public void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName) {
- log("deleteProviderResourceGroupRoles(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)",
- tenantDomain, service.getDomain().getName(), service.getName(), applicationName);
- runOrThrow(() -> {
- zmsClient.deleteProviderResourceGroupRoles(
- tenantDomain.getName(), service.getDomain().getName(), service.getName(), applicationName.id(), /*auditref*/null);
- });
- }
-
- @Override
- public boolean hasApplicationAccess(
- AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
- return hasAccess(
- action.name(), applicationResourceString(tenantDomain, applicationName), identity);
- }
-
- @Override
- public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) {
- return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), identity);
- }
-
- @Override
- public boolean hasHostedOperatorAccess(AthenzIdentity identity) {
- return getOrThrow(() -> hasAccess("modify", service.getDomain().getName() + ":hosted-vespa", identity));
- }
-
- /**
- * Used when creating tenancies. As there are no tenancy policies at this point,
- * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)}
- */
- @Override
- public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) {
- log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", identity);
- return getOrThrow(
- () -> zmsClient.getMembership(domain.getName(), "admin", identity.getFullName()).getIsMember());
- }
-
- @Override
- public List<AthenzDomain> getDomainList(String prefix) {
- log.log(LogLevel.DEBUG, String.format("getDomainList(prefix=%s)", prefix));
- return getOrThrow(
- () -> {
- DomainList domainList = zmsClient.getDomainList(
- /*limit*/null, /*skip*/null, prefix, /*depth*/null, /*domain*/null,
- /*productId*/ null, /*modifiedSince*/null);
- return toAthenzDomains(domainList.getNames());
- });
- }
-
- private static List<TenantRoleAction> createTenantRoleActions() {
- return Arrays.stream(ApplicationAction.values())
- .map(action -> new TenantRoleAction().setAction(action.name()).setRole(action.roleName))
- .collect(toList());
- }
-
- private static List<AthenzDomain> toAthenzDomains(List<String> domains) {
- return domains.stream().map(AthenzDomain::new).collect(toList());
- }
-
- private boolean hasAccess(String action, String resource, AthenzIdentity identity) {
- log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, identity);
- return getOrThrow(
- () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, identity.getFullName())
- .getGranted());
- }
-
- private static void log(String format, Object... args) {
- log.log(LogLevel.DEBUG, String.format(format, args));
- }
-
- private static void runOrThrow(Runnable wrappedCode) {
- try {
- wrappedCode.run();
- } catch (ZMSClientException e) {
- logWarning(e);
- throw new ZmsException(e.getCode(), e);
- }
- }
-
- private static <T> T getOrThrow(Supplier<T> wrappedCode) {
- try {
- return wrappedCode.get();
- } catch (ZMSClientException e) {
- logWarning(e);
- throw new ZmsException(e.getCode(), e);
- }
- }
-
- private static void logWarning(ZMSClientException e) {
- log.warning("Error from Athenz: " + e.getMessage());
- }
-
- private String resourceStringPrefix(AthenzDomain tenantDomain) {
- return String.format("%s:service.%s.tenant.%s",
- service.getDomain().getName(), service.getName(), tenantDomain.getName());
- }
-
- private String tenantResourceString(AthenzDomain tenantDomain) {
- return resourceStringPrefix(tenantDomain) + ".wildcard";
- }
-
- private String applicationResourceString(AthenzDomain tenantDomain, ApplicationId applicationName) {
- return resourceStringPrefix(tenantDomain) + "." + "res_group" + "." + applicationName.id() + ".wildcard";
- }
-
- private enum TenantAction {
- // This is meant to match only the '*' action of the 'admin' role.
- // If needed, we can replace it with 'create', 'delete' etc. later.
- _modify_
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java
index 6f829113016..f9f449121e0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzClientFactoryMock.java
@@ -3,11 +3,9 @@ package com.yahoo.vespa.hosted.controller.athenz.mock;
import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.client.zms.ZmsClient;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
import java.util.logging.Level;
@@ -36,28 +34,20 @@ public class AthenzClientFactoryMock extends AbstractComponent implements Athenz
}
@Override
- public AthenzIdentity getControllerIdentity() {
+ public AthenzService getControllerIdentity() {
return new AthenzService("vespa.hosting");
}
@Override
- public ZmsClient createZmsClientWithServicePrincipal() {
- log("createZmsClientWithServicePrincipal()");
- return new ZmsClientMock(athenz);
+ public ZmsClient createZmsClient() {
+ return new ZmsClientMock(athenz, getControllerIdentity());
}
@Override
- public ZtsClient createZtsClientWithServicePrincipal() {
- log("createZtsClientWithServicePrincipal()");
+ public ZtsClient createZtsClient() {
return new ZtsClientMock(athenz);
}
- @Override
- public ZmsClient createZmsClientWithAuthorizedServiceToken(NToken authorizedServiceToken) {
- log("createZmsClientWithAuthorizedServiceToken(authorizedServiceToken='%s')", authorizedServiceToken);
- return new ZmsClientMock(athenz);
- }
-
private static void log(String format, Object... args) {
log.log(Level.INFO, String.format(format, args));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java
index 0a360184da9..a11426b9a23 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.athenz.mock;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java
index 5e8674ce637..f7a8e702b06 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java
@@ -1,18 +1,26 @@
// 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.athenz.mock;
-import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
import com.yahoo.vespa.athenz.api.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsClient;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
+import com.yahoo.vespa.athenz.api.AthenzResourceName;
+import com.yahoo.vespa.athenz.api.AthenzRole;
+import com.yahoo.vespa.athenz.api.AthenzService;
+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 com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
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
@@ -22,20 +30,24 @@ public class ZmsClientMock implements ZmsClient {
private static final Logger log = Logger.getLogger(ZmsClientMock.class.getName());
private final AthenzDbMock athenz;
+ private final AthenzService 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) {
+ public ZmsClientMock(AthenzDbMock athenz, AthenzService controllerIdentity) {
this.athenz = athenz;
+ this.controllerIdentity = controllerIdentity;
}
@Override
- public void createTenant(AthenzDomain tenantDomain) {
- log("createTenant(tenantDomain='%s')", tenantDomain);
+ public void createTenancy(AthenzDomain tenantDomain, AthenzService providerService, OktaAccessToken token) {
+ log("createTenancy(tenantDomain='%s')", tenantDomain);
getDomainOrThrow(tenantDomain, false).isVespaTenant = true;
}
@Override
- public void deleteTenant(AthenzDomain tenantDomain) {
- log("deleteTenant(tenantDomain='%s')", tenantDomain);
+ public void deleteTenancy(AthenzDomain tenantDomain, AthenzService providerService, OktaAccessToken token) {
+ log("deleteTenancy(tenantDomain='%s')", tenantDomain);
AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, false);
domain.isVespaTenant = false;
domain.applications.clear();
@@ -43,55 +55,80 @@ public class ZmsClientMock implements ZmsClient {
}
@Override
- public void addApplication(AthenzDomain tenantDomain, ApplicationId applicationName) {
- log("addApplication(tenantDomain='%s', applicationName='%s')", tenantDomain, applicationName);
+ public void createProviderResourceGroup(AthenzDomain tenantDomain, AthenzService providerService, String resourceGroup, Set<RoleAction> roleActions, OktaAccessToken token) {
+ log("createProviderResourceGroup(tenantDomain='%s', resourceGroup='%s')", tenantDomain, resourceGroup);
AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, true);
- if (!domain.applications.containsKey(applicationName)) {
- domain.applications.put(applicationName, new AthenzDbMock.Application());
+ ApplicationId applicationId = new ApplicationId(resourceGroup);
+ if (!domain.applications.containsKey(applicationId)) {
+ domain.applications.put(applicationId, new AthenzDbMock.Application());
}
}
@Override
- public void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName) {
- log("addApplication(tenantDomain='%s', applicationName='%s')", tenantDomain, applicationName);
- getDomainOrThrow(tenantDomain, true).applications.remove(applicationName);
+ public void deleteProviderResourceGroup(AthenzDomain tenantDomain, AthenzService providerService, String resourceGroup, OktaAccessToken token) {
+ log("deleteProviderResourceGroup(tenantDomain='%s', resourceGroup='%s')", tenantDomain, resourceGroup);
+ getDomainOrThrow(tenantDomain, true).applications.remove(new ApplicationId(resourceGroup));
}
@Override
- public boolean hasApplicationAccess(AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) {
- log("hasApplicationAccess(principal='%s', action='%s', tenantDomain='%s', applicationName='%s')",
- identity, action, tenantDomain, applicationName);
- AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, true);
- AthenzDbMock.Application application = domain.applications.get(applicationName);
- if (application == null) {
- throw zmsException(400, "Application '%s' not found", applicationName);
+ public boolean getMembership(AthenzRole role, AthenzIdentity identity) {
+ if (role.roleName().equals("admin")) {
+ return getDomainOrThrow(role.domain(), false).admins.contains(identity);
}
- return isHostedOperator(identity) || domain.admins.contains(identity) || application.acl.get(action).contains(identity);
+ return false;
}
@Override
- public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) {
- log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", identity, tenantDomain);
- return isHostedOperator(identity) || isDomainAdmin(identity, tenantDomain) ||
- getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(identity);
+ public List<AthenzDomain> getDomainList(String prefix) {
+ log("getDomainList()");
+ return new ArrayList<>(athenz.domains.keySet());
}
@Override
- public boolean hasHostedOperatorAccess(AthenzIdentity identity) {
- log("hasHostedOperatorAccess(identity='%s')", identity);
- return isHostedOperator(identity);
+ 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;
+ }
+ return false;
}
@Override
- public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) {
- log("isDomainAdmin(principal='%s', domain='%s')", identity, domain);
- return getDomainOrThrow(domain, false).admins.contains(identity);
+ 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"));
}
- @Override
- public List<AthenzDomain> getDomainList(String prefix) {
- log("getDomainList()");
- return new ArrayList<>(athenz.domains.keySet());
+ 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) {
@@ -107,8 +144,8 @@ public class ZmsClientMock implements ZmsClient {
return athenz.hostedOperators.contains(identity);
}
- private static ZmsException zmsException(int code, String message, Object... args) {
- return new ZmsException(code, String.format(message, args));
+ 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) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 4da1e6d1f58..bcf0ff70c64 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -24,7 +24,8 @@ 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.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
+import com.yahoo.vespa.athenz.client.zms.ZmsClientException;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.AlreadyExistsException;
import com.yahoo.vespa.hosted.controller.Application;
@@ -46,7 +47,6 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Logs;
@@ -65,6 +65,7 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.JobStatus;
import com.yahoo.vespa.hosted.controller.application.RotationStatus;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
+import com.yahoo.vespa.hosted.controller.athenz.impl.ZmsClientFacade;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.MessageResponse;
import com.yahoo.vespa.hosted.controller.restapi.ResourceResponse;
@@ -109,7 +110,7 @@ import java.util.stream.Collectors;
public class ApplicationApiHandler extends LoggingRequestHandler {
private final Controller controller;
- private final AthenzClientFactory athenzClientFactory;
+ private final ZmsClientFacade zmsClient;
@Inject
public ApplicationApiHandler(LoggingRequestHandler.Context parentCtx,
@@ -117,7 +118,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
AthenzClientFactory athenzClientFactory) {
super(parentCtx);
this.controller = controller;
- this.athenzClientFactory = athenzClientFactory;
+ this.zmsClient = new ZmsClientFacade(athenzClientFactory.createZmsClient(), athenzClientFactory.getControllerIdentity());
}
@Override
@@ -688,7 +689,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if ( ! tenant.isPresent()) return ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist");
Inspector requestData = toSlime(request.getData()).get();
- NToken token = requireNToken(request, "Could not update " + tenantName);
+ OktaAccessToken token = requireOktaAccessToken(request, "Could not update " + tenantName);
controller.tenants().lockOrThrow(tenant.get().name(), lockedTenant -> {
lockedTenant = lockedTenant.with(new Property(mandatory("property", requestData).asString()));
@@ -715,17 +716,17 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
new Property(mandatory("property", requestData).asString()),
optional("propertyId", requestData).map(PropertyId::new));
throwIfNotAthenzDomainAdmin(tenant.domain(), request);
- controller.tenants().create(tenant, requireNToken(request, "Could not create " + tenantName));
+ controller.tenants().create(tenant, requireOktaAccessToken(request, "Could not create " + tenantName));
return tenant(tenant, request, true);
}
private HttpResponse createApplication(String tenantName, String applicationName, HttpRequest request) {
Application application;
try {
- application = controller.applications().createApplication(ApplicationId.from(tenantName, applicationName, "default"), getUserPrincipal(request).getNToken());
+ application = controller.applications().createApplication(ApplicationId.from(tenantName, applicationName, "default"), getOktaAccessToken(request));
}
- catch (ZmsException e) { // TODO: Push conversion down
- if (e.getCode() == com.yahoo.jdisc.Response.Status.FORBIDDEN)
+ catch (ZmsClientException e) { // TODO: Push conversion down
+ if (e.getErrorCode() == com.yahoo.jdisc.Response.Status.FORBIDDEN)
throw new ForbiddenException("Not authorized to create application", e);
else
throw e;
@@ -841,7 +842,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (tenant.get() instanceof AthenzTenant) {
controller.tenants().deleteTenant((AthenzTenant) tenant.get(),
- requireNToken(request, "Could not delete " + tenantName));
+ requireOktaAccessToken(request, "Could not delete " + tenantName));
} else if (tenant.get() instanceof UserTenant) {
controller.tenants().deleteTenant((UserTenant) tenant.get());
} else {
@@ -855,7 +856,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse deleteApplication(String tenantName, String applicationName, HttpRequest request) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
- controller.applications().deleteApplication(id, getUserPrincipal(request).getNToken());
+ controller.applications().deleteApplication(id, getOktaAccessToken(request));
return new EmptyJsonResponse(); // TODO: Replicates current behavior but should return a message response instead
}
@@ -1032,8 +1033,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private void throwIfNotAthenzDomainAdmin(AthenzDomain tenantDomain, HttpRequest request) {
AthenzIdentity identity = getUserPrincipal(request).getIdentity();
- boolean isDomainAdmin = athenzClientFactory.createZmsClientWithServicePrincipal()
- .isDomainAdmin(identity, tenantDomain);
+ boolean isDomainAdmin = zmsClient.isDomainAdmin(identity, tenantDomain);
if ( ! isDomainAdmin) {
throw new ForbiddenException(
String.format("The user '%s' is not admin in Athenz domain '%s'", identity.getFullName(), tenantDomain.getName()));
@@ -1225,9 +1225,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
throw new IllegalArgumentException("Unknown tenant type: " + tenant.getClass().getSimpleName());
}
- private static NToken requireNToken(HttpRequest request, String message) {
- return getUserPrincipal(request).getNToken().orElseThrow(() -> new IllegalArgumentException(
- message + ": No NToken provided"));
+ private static OktaAccessToken requireOktaAccessToken(HttpRequest request, String message) {
+ return getOktaAccessToken(request)
+ .orElseThrow(() -> new IllegalArgumentException(message + ": No Okta Access Token provided"));
+ }
+
+ private static Optional<OktaAccessToken> getOktaAccessToken(HttpRequest request) {
+ return Optional.ofNullable(request.getHeader(OktaAccessToken.HTTP_HEADER_NAME))
+ .map(OktaAccessToken::new);
}
private static ApplicationId appIdFromPath(Path path) {
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 378dc0315d6..59847437339 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
@@ -9,16 +9,17 @@ 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.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
-import com.yahoo.restapi.Path;
+import com.yahoo.vespa.hosted.controller.athenz.impl.ZmsClientFacade;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.UserTenant;
@@ -40,7 +41,7 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.HEAD;
import static com.yahoo.jdisc.http.HttpRequest.Method.OPTIONS;
import static com.yahoo.jdisc.http.HttpRequest.Method.POST;
import static com.yahoo.jdisc.http.HttpRequest.Method.PUT;
-import static com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities.SCREWDRIVER_DOMAIN;
+import static com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities.SCREWDRIVER_DOMAIN;
/**
* A security filter protects all controller apis.
@@ -55,7 +56,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
private static final Logger log = Logger.getLogger(ControllerAuthorizationFilter.class.getName());
- private final AthenzClientFactory clientFactory;
+ private final ZmsClientFacade zmsClient;
private final TenantController tenantController;
@Inject
@@ -63,7 +64,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
Controller controller,
CorsFilterConfig corsConfig) {
super(corsConfig);
- this.clientFactory = clientFactory;
+ this.zmsClient = new ZmsClientFacade(clientFactory.createZmsClient(), clientFactory.getControllerIdentity());
this.tenantController = controller.tenants();
}
@@ -71,7 +72,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
TenantController tenantController,
Set<String> allowedUrls) {
super(allowedUrls);
- this.clientFactory = clientFactory;
+ this.zmsClient = new ZmsClientFacade(clientFactory.createZmsClient(), clientFactory.getControllerIdentity());;
this.tenantController = tenantController;
}
@@ -152,8 +153,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
}
private boolean isHostedOperator(AthenzIdentity identity) {
- return clientFactory.createZmsClientWithServicePrincipal()
- .hasHostedOperatorAccess(identity);
+ return zmsClient.hasHostedOperatorAccess(identity);
}
private void verifyIsTenantAdmin(AthenzPrincipal principal, TenantName name) {
@@ -167,8 +167,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) {
if (tenant instanceof AthenzTenant) {
- return clientFactory.createZmsClientWithServicePrincipal()
- .hasTenantAdminAccess(identity, ((AthenzTenant) tenant).domain());
+ return zmsClient.hasTenantAdminAccess(identity, ((AthenzTenant) tenant).domain());
} else if (tenant instanceof UserTenant) {
if (!(identity instanceof AthenzUser)) {
return false;
@@ -211,13 +210,13 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase {
private boolean hasDeployerAccess(AthenzIdentity identity, AthenzDomain tenantDomain, ApplicationName application) {
try {
- return clientFactory.createZmsClientWithServicePrincipal()
+ return zmsClient
.hasApplicationAccess(
identity,
ApplicationAction.deploy,
tenantDomain,
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(application.value()));
- } catch (ZmsException e) {
+ } catch (ZmsClientException e) {
throw new InternalServerErrorException("Failed to authorize operation: (" + e.getMessage() + ")", e);
}
}
diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def
index f8d65c25e47..dc1a2337aaf 100644
--- a/controller-server/src/main/resources/configdefinitions/athenz.def
+++ b/controller-server/src/main/resources/configdefinitions/athenz.def
@@ -1,44 +1,15 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=vespa.hosted.controller.athenz.config
-# Principal header name
-principalHeaderName string default="Athenz-Principal-Auth"
-# TODO Remove once migrated to Okta
-
# URL to ZMS API endpoint
zmsUrl string
-# URL to legacy ZMS API endpoint
-legacyZmsUrl string
-# TODO Remove once migrated to Okta
-
# URL to ZTS API endpoint
ztsUrl string
# Athenz domain for controller identity. The domain is also used for Athenz tenancy integration.
domain string
-# Name of the internal user authentication passthru attribute
-userAuthenticationPassThruAttribute string
-# TODO Remove once migrated to Okta
-
-# Path to Athenz CA JKS trust store
-athenzCaTrustStore string
-
-# Certificate DNS domain
-certDnsDomain string
-
# Athenz service name for controller identity
service.name string
-# Athenz service public key id
-service.publicKeyId string
-
-# Version of Athenz service private key
-service.privateKeyVersion int
-
-# Name of Athenz service private key secret
-service.privateKeySecretName string
-
-# Expiry of service principal token and certificate
-service.credentialsExpiryMinutes int default=43200 # 30 days
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 1bec06ea0e0..3351efff2dd 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -11,7 +11,7 @@ import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -320,7 +320,7 @@ public class ControllerTest {
tester.deployAndNotify(app1, applicationPackage, true, systemTest);
tester.applications().deactivate(app1.id(), ZoneId.from(Environment.test, RegionName.from("us-east-1")));
tester.applications().deactivate(app1.id(), ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
- tester.applications().deleteApplication(app1.id(), Optional.of(new NToken("ntoken")));
+ tester.applications().deleteApplication(app1.id(), Optional.of(new OktaAccessToken("okta-token")));
try (RotationLock lock = tester.applications().rotationRepository().lock()) {
assertTrue("Rotation is unassigned",
tester.applications().rotationRepository().availableRotations(lock)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index 287fbe8c36d..8994c68acf3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.slime.Slime;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
@@ -247,14 +248,14 @@ public final class ControllerTester {
Optional.ofNullable(propertyId)
.map(Object::toString)
.map(PropertyId::new));
- controller().tenants().create(tenant, TestIdentities.userNToken);
+ controller().tenants().create(tenant, new OktaAccessToken("okta-token"));
assertNotNull(controller().tenants().tenant(name));
return name;
}
public Application createApplication(TenantName tenant, String applicationName, String instanceName, long projectId) {
ApplicationId applicationId = ApplicationId.from(tenant.value(), applicationName, instanceName);
- controller().applications().createApplication(applicationId, Optional.of(TestIdentities.userNToken));
+ controller().applications().createApplication(applicationId, Optional.of(new OktaAccessToken("okta-token")));
controller().applications().lockOrThrow(applicationId, lockedApplication ->
controller().applications().store(lockedApplication.withProjectId(OptionalLong.of(projectId))));
return controller().applications().require(applicationId);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java
deleted file mode 100644
index 18d3e92620d..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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;
-
-import com.yahoo.vespa.athenz.api.NToken;
-import com.yahoo.vespa.hosted.controller.api.identifiers.EnvironmentId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
-import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId;
-
-/**
- * @author Tony Vaagenes
- */
-public class TestIdentities {
-
- public static final EnvironmentId environment = new EnvironmentId("dev");
-
- public static final RegionId region = new RegionId("us-east-1");
-
- public static final InstanceId instance = new InstanceId("default");
-
- public static final Property property = new Property("property");
-
- public static final NToken userNToken = new NToken("dummy");
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java
index 4acd3a34c8d..72027234b28 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java
@@ -13,7 +13,6 @@ import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
-import com.yahoo.vespa.athenz.api.NToken;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.yolean.chain.Before;
@@ -24,7 +23,6 @@ import com.yahoo.yolean.chain.Before;
public class AthenzFilterMock implements SecurityRequestFilter {
public static final String IDENTITY_HEADER_NAME = "Athenz-Identity";
- public static final String ATHENZ_NTOKEN_HEADER_NAME = "Athenz-NToken";
private static final ObjectMapper mapper = new ObjectMapper();
@@ -32,7 +30,6 @@ public class AthenzFilterMock implements SecurityRequestFilter {
public void filter(DiscFilterRequest request, ResponseHandler handler) {
if (request.getMethod().equalsIgnoreCase("OPTIONS")) return;
String identityName = request.getHeader(IDENTITY_HEADER_NAME);
- String nToken = request.getHeader(ATHENZ_NTOKEN_HEADER_NAME);
if (identityName == null) {
Response response = new Response(HttpResponse.Status.UNAUTHORIZED);
response.headers().put("Content-Type", "application/json");
@@ -45,10 +42,7 @@ public class AthenzFilterMock implements SecurityRequestFilter {
}
} else {
AthenzIdentity identity = AthenzIdentities.from(identityName);
- AthenzPrincipal principal =
- nToken == null ?
- new AthenzPrincipal(identity) :
- new AthenzPrincipal(identity, new NToken(nToken));
+ AthenzPrincipal principal = new AthenzPrincipal(identity);
request.setUserPrincipal(principal);
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
index b950e969300..92f3e87f001 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DnsMaintainerTest.java
@@ -4,7 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
@@ -88,7 +88,7 @@ public class DnsMaintainerTest {
tester.deployAndNotify(application, applicationPackage, true, systemTest);
tester.applications().deactivate(application.id(), ZoneId.from(Environment.test, RegionName.from("us-east-1")));
tester.applications().deactivate(application.id(), ZoneId.from(Environment.staging, RegionName.from("us-east-3")));
- tester.applications().deleteApplication(application.id(), Optional.of(new NToken("ntoken")));
+ tester.applications().deleteApplication(application.id(), Optional.of(new OktaAccessToken("okta-token")));
// DnsMaintainer removes records
dnsMaintainer.maintain();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
index 13ac5c41644..ca31eb52979 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.vespa.hosted.controller.TestIdentities;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.application.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.application.SourceRevision;
@@ -49,11 +49,11 @@ import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateReal;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateTester;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deployReal;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deployTester;
+import static com.yahoo.vespa.hosted.controller.deployment.Step.endTests;
import static com.yahoo.vespa.hosted.controller.deployment.Step.installReal;
import static com.yahoo.vespa.hosted.controller.deployment.Step.installTester;
import static com.yahoo.vespa.hosted.controller.deployment.Step.report;
import static com.yahoo.vespa.hosted.controller.deployment.Step.startTests;
-import static com.yahoo.vespa.hosted.controller.deployment.Step.endTests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -211,7 +211,7 @@ public class JobRunnerTest {
// Thread is still trying to deploy tester -- delete application, and see all data is garbage collected.
assertEquals(Collections.singletonList(runId), jobs.active().stream().map(run -> run.id()).collect(Collectors.toList()));
- tester.controller().applications().deleteApplication(id, Optional.of(TestIdentities.userNToken));
+ tester.controller().applications().deleteApplication(id, Optional.of(new OktaAccessToken("okta-token")));
assertEquals(Collections.emptyList(), jobs.active());
assertEquals(runId, jobs.last(id, systemTest).get().id());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
index b571e6f1c48..11e8a82dd42 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java
@@ -6,16 +6,16 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.TestIdentities;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities;
+import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockBuildService;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
@@ -77,9 +77,9 @@ public class ContainerControllerTester {
controller().tenants().create(AthenzTenant.create(TenantName.from(tenant), domain1,
new Property("property1"),
Optional.of(new PropertyId("1234"))),
- TestIdentities.userNToken);
+ new OktaAccessToken("okta-token"));
ApplicationId app = ApplicationId.from(tenant, application, "default");
- return controller().applications().createApplication(app, Optional.of(TestIdentities.userNToken));
+ return controller().applications().createApplication(app, Optional.of(new OktaAccessToken("okta-token")));
}
public Application deploy(Application application, ApplicationPackage applicationPackage, ZoneId zone) {
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 e47cdf887f9..8364261696c 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
@@ -7,7 +7,7 @@ import com.yahoo.application.container.handler.Request;
import com.yahoo.application.container.handler.Response;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock;
import org.junit.After;
import org.junit.Before;
@@ -15,7 +15,6 @@ import org.junit.Before;
import java.io.UncheckedIOException;
import java.nio.charset.CharacterCodingException;
-import static com.yahoo.vespa.hosted.controller.integration.AthenzFilterMock.ATHENZ_NTOKEN_HEADER_NAME;
import static com.yahoo.vespa.hosted.controller.integration.AthenzFilterMock.IDENTITY_HEADER_NAME;
import static org.junit.Assert.assertEquals;
@@ -152,8 +151,8 @@ public class ControllerContainerTest {
return request;
}
- protected static Request addNTokenToRequest(Request request, NToken nToken) {
- request.getHeaders().put(ATHENZ_NTOKEN_HEADER_NAME, nToken.getRawToken());
+ protected static Request addOktaAccessToken(Request request, OktaAccessToken token) {
+ request.getHeaders().put(OktaAccessToken.HTTP_HEADER_NAME, token.token());
return request;
}
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 ab00ecab376..027fc9745f2 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
@@ -16,7 +16,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.application.v4.EnvironmentResource;
@@ -25,8 +25,8 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.MetricsService.ApplicationMetrics;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities;
+import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId;
@@ -110,7 +110,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345");
private static final UserId USER_ID = new UserId("myuser");
private static final UserId HOSTED_VESPA_OPERATOR = new UserId("johnoperator");
- private static final NToken N_TOKEN = new NToken("dummy");
+ private static final OktaAccessToken OKTA_AT = new OktaAccessToken("dummy");
private static final ZoneId TEST_ZONE = ZoneId.from(Environment.test, RegionName.from("us-east-1"));
private static final ZoneId STAGING_ZONE = ZoneId.from(Environment.staging, RegionName.from("us-east-3"));
@@ -146,12 +146,12 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
// PUT (modify) a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", PUT)
.userIdentity(USER_ID)
- .nToken(N_TOKEN)
+ .oktaAccessToken(OKTA_AT)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
// GET the authenticated user (with associated tenants)
@@ -170,13 +170,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (add) a tenant with property ID
tester.assertResponse(request("/application/v4/tenant/tenant2", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN)
+ .oktaAccessToken(OKTA_AT)
.data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"),
new File("tenant-without-applications-with-id.json"));
// PUT (modify) a tenant with property ID
tester.assertResponse(request("/application/v4/tenant/tenant2", PUT)
.userIdentity(USER_ID)
- .nToken(N_TOKEN)
+ .oktaAccessToken(OKTA_AT)
.data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"),
new File("tenant-without-applications-with-id.json"));
// GET a tenant with property ID and contact information
@@ -187,7 +187,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// POST (create) an application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
// GET a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", GET).userIdentity(USER_ID),
@@ -267,7 +267,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("application-reference-2.json"));
ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default");
@@ -293,7 +293,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE application
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"");
// GET tenant screwdriver projects
@@ -454,11 +454,11 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE an application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"");
// DELETE a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
}
@@ -530,13 +530,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Create tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
// Create application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
// Grant deploy access
@@ -671,21 +671,21 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
// POST (add) another tenant under the same domain
tester.assertResponse(request("/application/v4/tenant/tenant2", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create tenant 'tenant2': The Athens domain 'domain1' is already connected to tenant 'tenant1'\"}",
400);
// Add the same tenant again
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN)
+ .oktaAccessToken(OKTA_AT)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'tenant1' already exists\"}",
400);
@@ -694,7 +694,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/my_tenant_2", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"New tenant or application names must start with a letter, may contain no more than 20 characters, and may only contain lowercase letters, digits or dashes, but no double-dashes.\"}",
400);
@@ -702,14 +702,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/by-tenant2", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz tenant name cannot have prefix 'by-'\"}",
400);
// POST (create) an (empty) application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
// Create the same application again
@@ -752,14 +752,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE tenant which has an application
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not delete tenant 'tenant1': This tenant has active applications\"}",
400);
// DELETE application
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"");
// DELETE application again - should produce 404
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
@@ -769,7 +769,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// DELETE tenant
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
// DELETE tenant again - should produce 404
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE)
@@ -786,12 +786,12 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Create legancy tenant name containing underscores
tester.controller().tenants().create(new AthenzTenant(TenantName.from("my_tenant"), ATHENZ_TENANT_DOMAIN,
new Property("property1"), Optional.empty(), Optional.empty()),
- N_TOKEN);
+ OKTA_AT);
// POST (add) a Athenz tenant with dashes duplicates existing one with underscores
tester.assertResponse(request("/application/v4/tenant/my-tenant", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'my-tenant' already exists\"}",
400);
}
@@ -827,21 +827,21 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
.userIdentity(authorizedUser)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"),
200);
// Creating an application for an Athens domain the user is not admin for is disallowed
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
.userIdentity(unauthorizedUser)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\n \"code\" : 403,\n \"message\" : \"Tenant admin or Vespa operator role required\"\n}",
403);
// (Create it with the right tenant id)
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
.userIdentity(authorizedUser)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("application-reference.json"),
200);
@@ -862,7 +862,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// (Deleting it with the right tenant id)
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE)
.userIdentity(authorizedUser)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"",
200);
@@ -878,7 +878,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1", PUT)
.data("{\"athensDomain\":\"domain2\", \"property\":\"property1\"}")
.userIdentity(authorizedUser)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
"{\"tenant\":\"tenant1\",\"type\":\"ATHENS\",\"athensDomain\":\"domain2\",\"property\":\"property1\",\"applications\":[]}",
200);
@@ -1093,7 +1093,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
private final Request.Method method;
private byte[] data = new byte[0];
private AthenzIdentity identity;
- private NToken nToken;
+ private OktaAccessToken oktaAccessToken;
private String contentType = "application/json";
private String recursive;
@@ -1115,7 +1115,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
private RequestBuilder userIdentity(UserId userId) { this.identity = HostedAthenzIdentities.from(userId); return this; }
private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = HostedAthenzIdentities.from(screwdriverId); return this; }
- private RequestBuilder nToken(NToken nToken) { this.nToken = nToken; return this; }
+ private RequestBuilder oktaAccessToken(OktaAccessToken oktaAccessToken) { this.oktaAccessToken = oktaAccessToken; return this; }
private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; }
private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; }
@@ -1129,8 +1129,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
if (identity != null) {
addIdentityToRequest(request, identity);
}
- if (nToken != null) {
- addNTokenToRequest(request, nToken);
+ if (oktaAccessToken != null) {
+ addOktaAccessToken(request, oktaAccessToken);
}
return request;
}
@@ -1173,11 +1173,11 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1", POST)
.userIdentity(USER_ID)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("tenant-without-applications.json"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST)
.userIdentity(USER_ID)
- .nToken(N_TOKEN),
+ .oktaAccessToken(OKTA_AT),
new File("application-reference.json"));
addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN,
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1"));
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 9951e4499f7..c3b9c11de88 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
@@ -14,8 +14,8 @@ import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities;
+import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction;
+import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities;
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;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzResourceGroup.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzResourceGroup.java
new file mode 100644
index 00000000000..2825cf57c7b
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzResourceGroup.java
@@ -0,0 +1,40 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.api;
+
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class AthenzResourceGroup {
+ private final String name;
+
+ public AthenzResourceGroup(String name) {
+ this.name = name;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "AthenzResourceGroup{" +
+ "name='" + name + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AthenzResourceGroup that = (AthenzResourceGroup) o;
+ return Objects.equals(name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/OktaAccessToken.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/OktaAccessToken.java
new file mode 100644
index 00000000000..8b19f7abdd5
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/OktaAccessToken.java
@@ -0,0 +1,42 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.api;
+
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class OktaAccessToken {
+
+ public static final String HTTP_HEADER_NAME = "Okta-Access-Token";
+
+ private final String token;
+
+ public OktaAccessToken(String token) {
+ this.token = token;
+ }
+
+ public String token() {
+ return token;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OktaAccessToken that = (OktaAccessToken) o;
+ return Objects.equals(token, that.token);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(token);
+ }
+
+ @Override
+ public String toString() {
+ return "OktaAccessToken{" +
+ "token='" + token + '\'' +
+ '}';
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java
new file mode 100644
index 00000000000..02952d7c26b
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java
@@ -0,0 +1,95 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.common;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.yahoo.vespa.athenz.client.common.bindings.ErrorResponseEntity;
+import com.yahoo.vespa.athenz.identity.ServiceIdentitySslSocketFactory;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.eclipse.jetty.http.HttpStatus;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.time.Duration;
+import java.util.function.Supplier;
+
+/**
+ * @author bjorncs
+ */
+public abstract class ClientBase implements AutoCloseable {
+
+ private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
+
+ private final CloseableHttpClient client;
+ private final ClientExceptionFactory exceptionFactory;
+
+ protected ClientBase(String userAgent,
+ Supplier<SSLContext> sslContextSupplier,
+ ClientExceptionFactory exceptionFactory) {
+ this.exceptionFactory = exceptionFactory;
+ this.client = createHttpClient(userAgent, sslContextSupplier);
+ }
+
+ protected <T> T execute(HttpUriRequest request, ResponseHandler<T> responseHandler) {
+ try {
+ return client.execute(request, responseHandler);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ protected StringEntity toJsonStringEntity(Object entity) {
+ try {
+ return new StringEntity(objectMapper.writeValueAsString(entity), ContentType.APPLICATION_JSON);
+ } catch (JsonProcessingException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ protected <T> T readEntity(HttpResponse response, Class<T> entityType) throws IOException {
+ if (HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) {
+ return objectMapper.readValue(response.getEntity().getContent(), entityType);
+ } else {
+ ErrorResponseEntity errorEntity = objectMapper.readValue(response.getEntity().getContent(), ErrorResponseEntity.class);
+ throw exceptionFactory.createException(errorEntity.code, errorEntity.description);
+ }
+ }
+
+ private static CloseableHttpClient createHttpClient(String userAgent, Supplier<SSLContext> sslContextSupplier) {
+ return HttpClientBuilder.create()
+ .setRetryHandler(new DefaultHttpRequestRetryHandler(3, /*requestSentRetryEnabled*/true))
+ .setUserAgent(userAgent)
+ .setSSLSocketFactory(new SSLConnectionSocketFactory(new ServiceIdentitySslSocketFactory(sslContextSupplier), (HostnameVerifier)null))
+ .setDefaultRequestConfig(RequestConfig.custom()
+ .setConnectTimeout((int) Duration.ofSeconds(10).toMillis())
+ .setConnectionRequestTimeout((int)Duration.ofSeconds(10).toMillis())
+ .setSocketTimeout((int)Duration.ofSeconds(20).toMillis())
+ .build())
+ .build();
+ }
+
+ @Override
+ public void close() {
+ try {
+ this.client.close();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ protected interface ClientExceptionFactory {
+ RuntimeException createException(int errorCode, String description);
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/ErrorResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/bindings/ErrorResponseEntity.java
index 431af084f9f..acbb831194e 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/ErrorResponseEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/bindings/ErrorResponseEntity.java
@@ -1,5 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.athenz.client.zts.bindings;
+package com.yahoo.vespa.athenz.client.common.bindings;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/Pkcs10CsrSerializer.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/Pkcs10CsrSerializer.java
index ca33962c7c8..9b26f5f2517 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/Pkcs10CsrSerializer.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/Pkcs10CsrSerializer.java
@@ -1,5 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.athenz.client.zts.bindings.serializers;
+package com.yahoo.vespa.athenz.client.common.serializers;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateDeserializer.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/X509CertificateDeserializer.java
index 59f10a78a58..f948115d4f2 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateDeserializer.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/X509CertificateDeserializer.java
@@ -1,5 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.athenz.client.zts.bindings.serializers;
+package com.yahoo.vespa.athenz.client.common.serializers;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateListDeserializer.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/X509CertificateListDeserializer.java
index 64b23af9295..baa56512a62 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/X509CertificateListDeserializer.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/X509CertificateListDeserializer.java
@@ -1,5 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.athenz.client.zts.bindings.serializers;
+package com.yahoo.vespa.athenz.client.common.serializers;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/package-info.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/package-info.java
index 4c442617494..f1fe2a75b1b 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/serializers/package-info.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/serializers/package-info.java
@@ -3,6 +3,6 @@
* @author bjorncs
*/
@ExportPackage
-package com.yahoo.vespa.athenz.client.zts.bindings.serializers;
+package com.yahoo.vespa.athenz.client.common.serializers;
import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java
new file mode 100644
index 00000000000..ed9c1da56c2
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java
@@ -0,0 +1,141 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms;
+
+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.AthenzService;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
+import com.yahoo.vespa.athenz.client.common.ClientBase;
+import com.yahoo.vespa.athenz.client.zms.bindings.AccessResponseEntity;
+import com.yahoo.vespa.athenz.client.zms.bindings.DomainListResponseEntity;
+import com.yahoo.vespa.athenz.client.zms.bindings.MembershipResponseEntity;
+import com.yahoo.vespa.athenz.client.zms.bindings.ProviderResourceGroupRolesRequestEntity;
+import com.yahoo.vespa.athenz.client.zms.bindings.TenancyRequestEntity;
+import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
+import org.apache.http.Header;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.message.BasicHeader;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author bjorncs
+ */
+public class DefaultZmsClient extends ClientBase implements ZmsClient {
+
+ private final URI zmsUrl;
+ private final AthenzIdentity identity;
+
+ public DefaultZmsClient(URI zmsUrl, AthenzIdentity identity, SSLContext sslContext) {
+ this(zmsUrl, identity, () -> sslContext);
+ }
+
+ public DefaultZmsClient(URI zmsUrl, ServiceIdentityProvider identityProvider) {
+ this(zmsUrl, identityProvider.identity(), identityProvider::getIdentitySslContext);
+ }
+
+ private DefaultZmsClient(URI zmsUrl, AthenzIdentity identity, Supplier<SSLContext> sslContextSupplier) {
+ super("vespa-zms-client", sslContextSupplier, ZmsClientException::new);
+ this.zmsUrl = addTrailingSlash(zmsUrl);
+ this.identity = identity;
+ }
+
+ private static URI addTrailingSlash(URI zmsUrl) {
+ return zmsUrl.getPath().endsWith("/") ? zmsUrl : URI.create(zmsUrl.toString() + '/');
+ }
+
+ @Override
+ public void createTenancy(AthenzDomain tenantDomain, AthenzService providerService, OktaAccessToken token) {
+ URI uri = zmsUrl.resolve(String.format("domain/%s/tenancy/%s", tenantDomain.getName(), providerService.getFullName()));
+ HttpUriRequest request = RequestBuilder.put()
+ .setUri(uri)
+ .addHeader(creatOktaAccessTokenHeader(token))
+ .setEntity(toJsonStringEntity(new TenancyRequestEntity(tenantDomain, providerService, Collections.emptyList())))
+ .build();
+ execute(request, response -> {
+ readEntity(response, String.class);
+ return response.getStatusLine().getStatusCode();
+ });
+ }
+
+ @Override
+ public void deleteTenancy(AthenzDomain tenantDomain, AthenzService providerService, OktaAccessToken token) {
+ URI uri = zmsUrl.resolve(String.format("domain/%s/tenancy/%s", tenantDomain.getName(), providerService.getFullName()));
+ HttpUriRequest request = RequestBuilder.delete()
+ .setUri(uri)
+ .addHeader(creatOktaAccessTokenHeader(token))
+ .build();
+ execute(request, response -> readEntity(response, String.class));
+ }
+
+ @Override
+ public void createProviderResourceGroup(AthenzDomain tenantDomain, AthenzService providerService, String resourceGroup, Set<RoleAction> roleActions, OktaAccessToken token) {
+ URI uri = zmsUrl.resolve(String.format("domain/%s/provDomain/%s/provService/%s/resourceGroup/%s", tenantDomain.getName(), providerService.getDomainName(), providerService.getName(), resourceGroup));
+ HttpUriRequest request = RequestBuilder.put()
+ .setUri(uri)
+ .addHeader(creatOktaAccessTokenHeader(token))
+ .setEntity(toJsonStringEntity(new ProviderResourceGroupRolesRequestEntity(providerService, tenantDomain, roleActions, resourceGroup)))
+ .build();
+ execute(request, response -> readEntity(response, String.class)); // The ZMS API will return a json object that is similar to ProviderResourceGroupRolesRequestEntity
+ }
+
+ @Override
+ public void deleteProviderResourceGroup(AthenzDomain tenantDomain, AthenzService providerService, String resourceGroup, OktaAccessToken token) {
+ URI uri = zmsUrl.resolve(String.format("domain/%s/provDomain/%s/provService/%s/resourceGroup/%s", tenantDomain.getName(), providerService.getDomainName(), providerService.getName(), resourceGroup));
+ HttpUriRequest request = RequestBuilder.delete()
+ .setUri(uri)
+ .addHeader(creatOktaAccessTokenHeader(token))
+ .build();
+ execute(request, response -> readEntity(response, String.class));
+ }
+
+ @Override
+ public boolean getMembership(AthenzRole role, AthenzIdentity identity) {
+ URI uri = zmsUrl.resolve(String.format("domain/%s/role/%s/member/%s", role.domain().getName(), role.roleName(), identity.getFullName()));
+ HttpUriRequest request = RequestBuilder.get()
+ .setUri(uri)
+ .build();
+ return execute(request, response -> {
+ MembershipResponseEntity membership = readEntity(response, MembershipResponseEntity.class);
+ return membership.isMember;
+ });
+ }
+
+ @Override
+ public List<AthenzDomain> getDomainList(String prefix) {
+ HttpUriRequest request = RequestBuilder.get()
+ .setUri(zmsUrl.resolve("domain"))
+ .addParameter("prefix", prefix)
+ .build();
+ return execute(request, response -> {
+ DomainListResponseEntity result = readEntity(response, DomainListResponseEntity.class);
+ return result.domains.stream().map(AthenzDomain::new).collect(toList());
+ });
+ }
+
+ @Override
+ public boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity) {
+ URI uri = zmsUrl.resolve(String.format("access/%s/%s", action, resource.toResourceNameString()));
+ HttpUriRequest request = RequestBuilder.get()
+ .setUri(uri)
+ .build();
+ return execute(request, response -> {
+ AccessResponseEntity result = readEntity(response, AccessResponseEntity.class);
+ return result.granted;
+ });
+ }
+
+ private static Header creatOktaAccessTokenHeader(OktaAccessToken token) {
+ return new BasicHeader("Cookie", String.format("okta_at=%s", token.token()));
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/RoleAction.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/RoleAction.java
new file mode 100644
index 00000000000..405dd1aa56a
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/RoleAction.java
@@ -0,0 +1,49 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms;
+
+import com.yahoo.vespa.athenz.api.AthenzRole;
+
+import java.util.Objects;
+
+/**
+ * @author bjorncs
+ */
+public class RoleAction {
+ private final String roleName;
+ private final String action;
+
+ public RoleAction(String roleName, String action) {
+ this.roleName = roleName;
+ this.action = action;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ @Override
+ public String toString() {
+ return "RoleAction{" +
+ "roleName=" + roleName +
+ ", action='" + action + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RoleAction that = (RoleAction) o;
+ return Objects.equals(roleName, that.roleName) &&
+ Objects.equals(action, that.action);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(roleName, action);
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java
new file mode 100644
index 00000000000..cf044edeac0
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java
@@ -0,0 +1,35 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms;
+
+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.AthenzService;
+import com.yahoo.vespa.athenz.api.OktaAccessToken;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author bjorncs
+ */
+public interface ZmsClient extends AutoCloseable {
+
+ void createTenancy(AthenzDomain tenantDomain, AthenzService providerService, OktaAccessToken token);
+
+ void deleteTenancy(AthenzDomain tenantDomain, AthenzService providerService, OktaAccessToken token);
+
+ void createProviderResourceGroup(AthenzDomain tenantDomain, AthenzService providerService, String resourceGroup, Set<RoleAction> roleActions, OktaAccessToken token);
+
+ void deleteProviderResourceGroup(AthenzDomain tenantDomain, AthenzService providerService, String resourceGroup, OktaAccessToken token);
+
+ boolean getMembership(AthenzRole role, AthenzIdentity identity);
+
+ List<AthenzDomain> getDomainList(String prefix);
+
+ boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity);
+
+ void close();
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClientException.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClientException.java
new file mode 100644
index 00000000000..f1b3ab8e7da
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClientException.java
@@ -0,0 +1,32 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms;
+
+/**
+ * An exception that can be thrown by {@link ZmsClient} implementations.
+ *
+ * @author bjorncs
+ */
+public class ZmsClientException extends RuntimeException {
+
+ private final int errorCode;
+ private final String description;
+
+ public ZmsClientException(int errorCode, String description) {
+ super(createMessage(errorCode, description));
+ this.errorCode = errorCode;
+ this.description = description;
+ }
+
+ public int getErrorCode() {
+ return errorCode;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ private static String createMessage(int code, String description) {
+ return String.format("Received error from ZMS: code=%d, message=\"%s\"", code, description);
+ }
+
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/AccessResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/AccessResponseEntity.java
new file mode 100644
index 00000000000..dcc17bc807a
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/AccessResponseEntity.java
@@ -0,0 +1,19 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms.bindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AccessResponseEntity {
+ public final boolean granted;
+
+ @JsonCreator
+ public AccessResponseEntity(@JsonProperty("granted") boolean granted) {
+ this.granted = granted;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/DomainListResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/DomainListResponseEntity.java
new file mode 100644
index 00000000000..938d85dd74f
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/DomainListResponseEntity.java
@@ -0,0 +1,21 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms.bindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DomainListResponseEntity {
+ public final List<String> domains;
+
+ @JsonCreator
+ public DomainListResponseEntity(@JsonProperty("names") List<String> domains) {
+ this.domains = domains;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipResponseEntity.java
new file mode 100644
index 00000000000..499afb48f25
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipResponseEntity.java
@@ -0,0 +1,28 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms.bindings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MembershipResponseEntity {
+ public final String memberName;
+ public final boolean isMember;
+ public final String roleName;
+ public final String expiration;
+
+ @JsonCreator
+ public MembershipResponseEntity(@JsonProperty("memberName") String memberName,
+ @JsonProperty("isMember") boolean isMember,
+ @JsonProperty("roleName") String roleName,
+ @JsonProperty("expiration") String expiration) {
+ this.memberName = memberName;
+ this.isMember = isMember;
+ this.roleName = roleName;
+ this.expiration = expiration;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ProviderResourceGroupRolesRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ProviderResourceGroupRolesRequestEntity.java
new file mode 100644
index 00000000000..dccd18fed61
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ProviderResourceGroupRolesRequestEntity.java
@@ -0,0 +1,56 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms.bindings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.client.zms.RoleAction;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * @author bjorncs
+ */
+public class ProviderResourceGroupRolesRequestEntity {
+
+ @JsonProperty("domain")
+ private final String domain;
+
+ @JsonProperty("service")
+ private final String service;
+
+ @JsonProperty("tenant")
+ private final String tenant;
+
+ @JsonProperty("roles")
+ private final List<TenantRoleAction> roles;
+
+ @JsonProperty("resourceGroup")
+ private final String resourceGroup;
+
+ public ProviderResourceGroupRolesRequestEntity(AthenzService providerService, AthenzDomain tenantDomain, Set<RoleAction> rolesActions, String resourceGroup) {
+ this.domain = providerService.getDomainName();
+ this.service = providerService.getName();
+ this.tenant = tenantDomain.getName();
+ this.roles = rolesActions.stream().map(roleAction -> new TenantRoleAction(roleAction.getRoleName(), roleAction.getAction())).collect(toList());
+ this.resourceGroup = resourceGroup;
+ }
+
+ public static class TenantRoleAction {
+ @JsonProperty("role")
+ private final String role;
+
+ @JsonProperty("action")
+ private final String action;
+
+ public TenantRoleAction(String role, String action) {
+ this.role = role;
+ this.action = action;
+ }
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/TenancyRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/TenancyRequestEntity.java
new file mode 100644
index 00000000000..7883a505c71
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/TenancyRequestEntity.java
@@ -0,0 +1,31 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.client.zms.bindings;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzService;
+
+import java.util.List;
+
+/**
+ * @author bjorncs
+ */
+public class TenancyRequestEntity {
+
+ @JsonProperty("domain")
+ private final String tenantDomain;
+
+ @JsonProperty("service")
+ private final String providerService;
+
+ @JsonProperty("resourceGroups")
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private final List<String> resourceGroups;
+
+ public TenancyRequestEntity(AthenzDomain tenantDomain, AthenzService providerService, List<String> resourceGroups) {
+ this.tenantDomain = tenantDomain.getName();
+ this.providerService = providerService.getFullName();
+ this.resourceGroups = resourceGroups;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java
index e9aba31cf56..9eef2ff9903 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java
@@ -1,16 +1,13 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.client.zts;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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.AthenzService;
import com.yahoo.vespa.athenz.api.NToken;
import com.yahoo.vespa.athenz.api.ZToken;
-import com.yahoo.vespa.athenz.client.zts.bindings.ErrorResponseEntity;
+import com.yahoo.vespa.athenz.client.common.ClientBase;
import com.yahoo.vespa.athenz.client.zts.bindings.IdentityRefreshRequestEntity;
import com.yahoo.vespa.athenz.client.zts.bindings.IdentityResponseEntity;
import com.yahoo.vespa.athenz.client.zts.bindings.InstanceIdentityCredentials;
@@ -22,25 +19,13 @@ import com.yahoo.vespa.athenz.client.zts.bindings.RoleTokenResponseEntity;
import com.yahoo.vespa.athenz.client.zts.bindings.TenantDomainsResponseEntity;
import com.yahoo.vespa.athenz.client.zts.utils.IdentityCsrGenerator;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
-import com.yahoo.vespa.athenz.identity.ServiceIdentitySslSocketFactory;
import com.yahoo.security.Pkcs10Csr;
import org.apache.http.HttpResponse;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.entity.ContentType;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.eclipse.jetty.http.HttpStatus;
-
-import javax.net.ssl.HostnameVerifier;
+
import javax.net.ssl.SSLContext;
import java.io.IOException;
-import java.io.UncheckedIOException;
import java.net.URI;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
@@ -56,12 +41,9 @@ import static java.util.stream.Collectors.toList;
* @author bjorncs
* @author mortent
*/
-public class DefaultZtsClient implements ZtsClient {
-
- private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
+public class DefaultZtsClient extends ClientBase implements ZtsClient {
private final URI ztsUrl;
- private final CloseableHttpClient client;
private final AthenzIdentity identity;
public DefaultZtsClient(URI ztsUrl, AthenzIdentity identity, SSLContext sslContext) {
@@ -73,9 +55,9 @@ public class DefaultZtsClient implements ZtsClient {
}
private DefaultZtsClient(URI ztsUrl, AthenzIdentity identity, Supplier<SSLContext> sslContextSupplier) {
+ super("vespa-zts-client", sslContextSupplier, ZtsClientException::new);
this.ztsUrl = addTrailingSlash(ztsUrl);
this.identity = identity;
- this.client = createHttpClient(sslContextSupplier);
}
@Override
@@ -91,7 +73,7 @@ public class DefaultZtsClient implements ZtsClient {
.setUri(ztsUrl.resolve("instance/"))
.setEntity(toJsonStringEntity(payload))
.build();
- return execute(request, DefaultZtsClient::getInstanceIdentity);
+ return execute(request, this::getInstanceIdentity);
}
@Override
@@ -111,7 +93,7 @@ public class DefaultZtsClient implements ZtsClient {
.setUri(uri)
.setEntity(toJsonStringEntity(payload))
.build();
- return execute(request, DefaultZtsClient::getInstanceIdentity);
+ return execute(request, this::getInstanceIdentity);
}
@Override
@@ -189,30 +171,13 @@ public class DefaultZtsClient implements ZtsClient {
});
}
- private <T> T execute(HttpUriRequest request, ResponseHandler<T> responseHandler) {
- try {
- return client.execute(request, responseHandler);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private static InstanceIdentity getInstanceIdentity(HttpResponse response) throws IOException {
+ private InstanceIdentity getInstanceIdentity(HttpResponse response) throws IOException {
InstanceIdentityCredentials entity = readEntity(response, InstanceIdentityCredentials.class);
return entity.getServiceToken() != null
? new InstanceIdentity(entity.getX509Certificate(), new NToken(entity.getServiceToken()))
: new InstanceIdentity(entity.getX509Certificate());
}
- private static <T> T readEntity(HttpResponse response, Class<T> entityType) throws IOException {
- if (HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) {
- return objectMapper.readValue(response.getEntity().getContent(), entityType);
- } else {
- ErrorResponseEntity errorEntity = objectMapper.readValue(response.getEntity().getContent(), ErrorResponseEntity.class);
- throw new ZtsClientException(errorEntity.code, errorEntity.description);
- }
- }
-
private static URI addTrailingSlash(URI ztsUrl) {
if (ztsUrl.getPath().endsWith("/"))
return ztsUrl;
@@ -220,34 +185,4 @@ public class DefaultZtsClient implements ZtsClient {
return URI.create(ztsUrl.toString() + '/');
}
- private static StringEntity toJsonStringEntity(Object entity) {
- try {
- return new StringEntity(objectMapper.writeValueAsString(entity), ContentType.APPLICATION_JSON);
- } catch (JsonProcessingException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private static CloseableHttpClient createHttpClient(Supplier<SSLContext> sslContextSupplier) {
- return HttpClientBuilder.create()
- .setRetryHandler(new DefaultHttpRequestRetryHandler(3, /*requestSentRetryEnabled*/true))
- .setUserAgent("vespa-zts-client")
- .setSSLSocketFactory(new SSLConnectionSocketFactory(new ServiceIdentitySslSocketFactory(sslContextSupplier), (HostnameVerifier)null))
- .setDefaultRequestConfig(RequestConfig.custom()
- .setConnectTimeout((int)Duration.ofSeconds(10).toMillis())
- .setConnectionRequestTimeout((int)Duration.ofSeconds(10).toMillis())
- .setSocketTimeout((int)Duration.ofSeconds(20).toMillis())
- .build())
- .build();
- }
-
- @Override
- public void close() {
- try {
- this.client.close();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java
index 808c1162ef1..0704fef2ae3 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityRefreshRequestEntity.java
@@ -3,7 +3,7 @@ package com.yahoo.vespa.athenz.client.zts.bindings;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.Pkcs10CsrSerializer;
+import com.yahoo.vespa.athenz.client.common.serializers.Pkcs10CsrSerializer;
import com.yahoo.security.Pkcs10Csr;
/**
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java
index 7bd04362599..f36858ef7b8 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/IdentityResponseEntity.java
@@ -5,8 +5,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateDeserializer;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateListDeserializer;
+import com.yahoo.vespa.athenz.client.common.serializers.X509CertificateDeserializer;
+import com.yahoo.vespa.athenz.client.common.serializers.X509CertificateListDeserializer;
import java.security.cert.X509Certificate;
import java.util.List;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java
index 0ab697a1c4c..b9baba85ea1 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceIdentityCredentials.java
@@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateDeserializer;
+import com.yahoo.vespa.athenz.client.common.serializers.X509CertificateDeserializer;
import java.security.cert.X509Certificate;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java
index 0e7e94e96ac..fee91dbc15b 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/InstanceRefreshInformation.java
@@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.Pkcs10CsrSerializer;
+import com.yahoo.vespa.athenz.client.common.serializers.Pkcs10CsrSerializer;
import com.yahoo.security.Pkcs10Csr;
/**
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java
index 1b974bcc6fc..89bfce91154 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateRequestEntity.java
@@ -8,7 +8,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.Pkcs10CsrSerializer;
+import com.yahoo.vespa.athenz.client.common.serializers.Pkcs10CsrSerializer;
import com.yahoo.security.Pkcs10Csr;
import java.io.IOException;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java
index e80f5626843..857bfad9143 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/RoleCertificateResponseEntity.java
@@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.yahoo.vespa.athenz.client.zts.bindings.serializers.X509CertificateDeserializer;
+import com.yahoo.vespa.athenz.client.common.serializers.X509CertificateDeserializer;
import java.security.cert.X509Certificate;
import java.time.Instant;