diff options
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; |