From b1dd451e2d24d36fa3932e8208969e9f8b938e11 Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Tue, 29 Oct 2019 15:02:43 +0100 Subject: Pass Okta identity token to Athenz tenancy operations --- .../controller/athenz/impl/AthenzFacade.java | 26 ++--- .../security/AthenzAccessControlRequests.java | 17 +++- .../controller/security/AthenzCredentials.java | 20 ++-- .../vespa/hosted/controller/ControllerTester.java | 7 +- .../controller/integration/AthenzFilterMock.java | 3 + .../restapi/ContainerControllerTester.java | 4 +- .../restapi/ControllerContainerTest.java | 7 ++ .../restapi/application/ApplicationApiTest.java | 107 +++++++++++---------- 8 files changed, 115 insertions(+), 76 deletions(-) (limited to 'controller-server') diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java index 304a47044a1..70c504dd220 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java @@ -12,6 +12,7 @@ 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.api.OktaIdentityToken; import com.yahoo.vespa.athenz.client.zms.RoleAction; import com.yahoo.vespa.athenz.client.zms.ZmsClient; import com.yahoo.vespa.athenz.client.zms.ZmsClientException; @@ -85,7 +86,7 @@ public class AthenzFacade implements AccessControl { } else { // Create tenant resources in Athenz if domain is not already taken. log("createTenancy(tenantDomain=%s, service=%s)", domain, service); - zmsClient.createTenancy(domain, service, athenzCredentials.token()); + zmsClient.createTenancy(domain, service, athenzCredentials.identityToken(), athenzCredentials.accessToken()); } return tenant; @@ -121,14 +122,14 @@ public class AthenzFacade implements AccessControl { } else { // Delete and recreate tenant, and optionally application, resources in Athenz otherwise. log("createTenancy(tenantDomain=%s, service=%s)", newDomain, service); - zmsClient.createTenancy(newDomain, service, athenzCredentials.token()); + zmsClient.createTenancy(newDomain, service, athenzCredentials.identityToken(), athenzCredentials.accessToken()); for (Application application : applications) - createApplication(newDomain, application.id().application(), athenzCredentials.token()); + createApplication(newDomain, application.id().application(), athenzCredentials.identityToken(), athenzCredentials.accessToken()); log("deleteTenancy(tenantDomain=%s, service=%s)", oldDomain, service); for (Application application : applications) - deleteApplication(oldDomain, application.id().application(), athenzCredentials.token()); - zmsClient.deleteTenancy(oldDomain, service, athenzCredentials.token()); + deleteApplication(oldDomain, application.id().application(), athenzCredentials.identityToken(), athenzCredentials.accessToken()); + zmsClient.deleteTenancy(oldDomain, service, athenzCredentials.identityToken(), athenzCredentials.accessToken()); } return tenant; @@ -139,22 +140,22 @@ public class AthenzFacade implements AccessControl { AthenzCredentials athenzCredentials = (AthenzCredentials) credentials; log("deleteTenancy(tenantDomain=%s, service=%s)", athenzCredentials.domain(), service); - zmsClient.deleteTenancy(athenzCredentials.domain(), service, athenzCredentials.token()); + zmsClient.deleteTenancy(athenzCredentials.domain(), service, athenzCredentials.identityToken(), athenzCredentials.accessToken()); } @Override public void createApplication(TenantAndApplicationId id, Credentials credentials) { AthenzCredentials athenzCredentials = (AthenzCredentials) credentials; - createApplication(athenzCredentials.domain(), id.application(), athenzCredentials.token()); + createApplication(athenzCredentials.domain(), id.application(), athenzCredentials.identityToken(), athenzCredentials.accessToken()); } - private void createApplication(AthenzDomain domain, ApplicationName application, OktaAccessToken token) { + private void createApplication(AthenzDomain domain, ApplicationName application, OktaIdentityToken identityToken, OktaAccessToken accessToken) { Set tenantRoleActions = createTenantRoleActions(); log("createProviderResourceGroup(" + "tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)", domain, service.getDomain().getName(), service.getName(), application, tenantRoleActions); try { - zmsClient.createProviderResourceGroup(domain, service, application.value(), tenantRoleActions, token); + zmsClient.createProviderResourceGroup(domain, service, application.value(), tenantRoleActions, identityToken, accessToken); } catch (ZmsClientException e) { if (e.getErrorCode() == com.yahoo.jdisc.Response.Status.FORBIDDEN) @@ -169,7 +170,8 @@ public class AthenzFacade implements AccessControl { AthenzCredentials athenzCredentials = (AthenzCredentials) credentials; log("deleteProviderResourceGroup(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)", athenzCredentials.domain(), service.getDomain().getName(), service.getName(), id.application()); - zmsClient.deleteProviderResourceGroup(athenzCredentials.domain(), service, id.application().value(), athenzCredentials.token()); + zmsClient.deleteProviderResourceGroup(athenzCredentials.domain(), service, id.application().value(), + athenzCredentials.identityToken(), athenzCredentials.accessToken()); } @Override @@ -182,10 +184,10 @@ public class AthenzFacade implements AccessControl { .collect(Collectors.toUnmodifiableList()); } - private void deleteApplication(AthenzDomain domain, ApplicationName application, OktaAccessToken token) { + private void deleteApplication(AthenzDomain domain, ApplicationName application, OktaIdentityToken identityToken, OktaAccessToken accessToken) { log("deleteProviderResourceGroup(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)", domain, service.getDomain().getName(), service.getName(), application); - zmsClient.deleteProviderResourceGroup(domain, service, application.value(), token); + zmsClient.deleteProviderResourceGroup(domain, service, application.value(), identityToken, accessToken); } public boolean hasApplicationAccess( diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java index e658d65248e..38faab1483d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java @@ -7,6 +7,7 @@ import com.yahoo.slime.Inspector; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.OktaAccessToken; +import com.yahoo.vespa.athenz.api.OktaIdentityToken; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TenantController; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; @@ -16,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import java.security.Principal; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; /** * Extracts access control data for Athenz or user tenants from HTTP requests. @@ -44,13 +46,22 @@ public class AthenzAccessControlRequests implements AccessControlRequests { return new AthenzCredentials(requireAthenzPrincipal(request), tenants.get(tenant).map(AthenzTenant.class::cast).map(AthenzTenant::domain) .orElseGet(() -> new AthenzDomain(required("athensDomain", requestObject))), + requireOktaIdentityToken(request), requireOktaAccessToken(request)); } + private static OktaIdentityToken requireOktaIdentityToken(HttpRequest request) { + return requireToken(request, OktaIdentityToken::new, "okta.identity-token", "No Okta Identity Token provided"); + } + private static OktaAccessToken requireOktaAccessToken(HttpRequest request) { - return Optional.ofNullable(request.context().get("okta.access-token")) - .map(attribute -> new OktaAccessToken((String) attribute)) - .orElseThrow(() -> new IllegalArgumentException("No Okta Access Token provided")); + return requireToken(request, OktaAccessToken::new, "okta.access-token", "No Okta Access Token provided"); + } + + private static T requireToken(HttpRequest request, Function tokenFactory, String attribute, String errorMessage) { + return Optional.ofNullable(request.context().get(attribute)) + .map(value -> tokenFactory.apply((String) value)) + .orElseThrow(() -> new IllegalArgumentException(errorMessage)); } private static String required(String fieldName, Inspector object) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java index c9e3e249668..57c9e037f6a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java @@ -3,8 +3,7 @@ package com.yahoo.vespa.hosted.controller.security; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.OktaAccessToken; - -import java.util.Optional; +import com.yahoo.vespa.athenz.api.OktaIdentityToken; import static java.util.Objects.requireNonNull; @@ -18,12 +17,15 @@ import static java.util.Objects.requireNonNull; public class AthenzCredentials extends Credentials { private final AthenzDomain domain; - private final OktaAccessToken token; + private final OktaIdentityToken identityToken; + private final OktaAccessToken accessToken; - public AthenzCredentials(AthenzPrincipal user, AthenzDomain domain, OktaAccessToken token) { + public AthenzCredentials(AthenzPrincipal user, AthenzDomain domain, + OktaIdentityToken identityToken, OktaAccessToken accessToken) { super(user); this.domain = requireNonNull(domain); - this.token = requireNonNull(token); + this.accessToken = requireNonNull(accessToken); + this.identityToken = requireNonNull(identityToken); } @Override @@ -32,7 +34,11 @@ public class AthenzCredentials extends Credentials { /** Returns the Athenz domain of the tenant on whose behalf this request is made. */ public AthenzDomain domain() { return domain; } - /** Returns the token proving access to the requested action under this domain. */ - public OktaAccessToken token() { return token; } + /** Returns the Okta access token required for Athenz tenancy operation */ + public OktaAccessToken accessToken() { return accessToken; } + + /** /** Returns the Okta identity token required for Athenz tenancy operation */ + public OktaIdentityToken identityToken() { return identityToken; } + } 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 6e204a418ad..a3b2861b387 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 @@ -13,6 +13,7 @@ import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.athenz.api.OktaAccessToken; +import com.yahoo.vespa.athenz.api.OktaIdentityToken; import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; @@ -258,7 +259,8 @@ public final class ControllerTester { domain, new Property("Property" + propertyId), Optional.ofNullable(propertyId).map(Object::toString).map(PropertyId::new)); - AthenzCredentials credentials = new AthenzCredentials(new AthenzPrincipal(user), domain, new OktaAccessToken("okta-token")); + AthenzCredentials credentials = new AthenzCredentials( + new AthenzPrincipal(user), domain, new OktaIdentityToken("okta-identity-token"), new OktaAccessToken("okta-access-token")); controller().tenants().create(tenantSpec, credentials); if (contact.isPresent()) controller().tenants().lockOrThrow(name, LockedTenant.Athenz.class, tenant -> @@ -279,7 +281,8 @@ public final class ControllerTester { public Optional credentialsFor(TenantAndApplicationId id) { return domainOf(id).map(domain -> new AthenzCredentials(new AthenzPrincipal(new AthenzUser("user")), domain, - new OktaAccessToken("okta-token"))); + new OktaIdentityToken("okta-identity-token"), + new OktaAccessToken("okta-access-token"))); } public Application createApplication(TenantName tenant, String applicationName, String instanceName) { 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 8eb64a00d40..6cc6ca012c7 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 @@ -25,6 +25,7 @@ import java.util.Optional; public class AthenzFilterMock implements SecurityRequestFilter { public static final String IDENTITY_HEADER_NAME = "Athenz-Identity"; + public static final String OKTA_IDENTITY_TOKEN_HEADER_NAME = "Okta-Identity-Token"; public static final String OKTA_ACCESS_TOKEN_HEADER_NAME = "Okta-Access-Token"; private static final ObjectMapper mapper = new ObjectMapper(); @@ -47,6 +48,8 @@ public class AthenzFilterMock implements SecurityRequestFilter { AthenzPrincipal principal = new AthenzPrincipal(identity); request.setUserPrincipal(principal); } + Optional.ofNullable(request.getHeader(OKTA_IDENTITY_TOKEN_HEADER_NAME)) + .ifPresent(header -> request.setAttribute("okta.identity-token", header)); Optional.ofNullable(request.getHeader(OKTA_ACCESS_TOKEN_HEADER_NAME)) .ifPresent(header -> request.setAttribute("okta.access-token", header)); } 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 912d285fb52..110aaf2b1a6 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 @@ -11,6 +11,7 @@ import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzPrincipal; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.athenz.api.OktaAccessToken; +import com.yahoo.vespa.athenz.api.OktaIdentityToken; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions; @@ -72,7 +73,8 @@ public class ContainerControllerTester { public Application createApplication(String athensDomain, String tenant, String application, String instance) { AthenzDomain domain1 = addTenantAthenzDomain(athensDomain, "user"); AthenzPrincipal user = new AthenzPrincipal(new AthenzUser("user")); - AthenzCredentials credentials = new AthenzCredentials(user, domain1, new OktaAccessToken("okta-token")); + AthenzCredentials credentials = new AthenzCredentials( + user, domain1, new OktaIdentityToken("okta-identity-token"), new OktaAccessToken("okta-access-token")); AthenzTenantSpec tenantSpec = new AthenzTenantSpec(TenantName.from(tenant), domain1, new Property("property1"), 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 f08dc18a58b..fb0e92ab7f4 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 @@ -9,6 +9,7 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.athenz.api.OktaAccessToken; +import com.yahoo.vespa.athenz.api.OktaIdentityToken; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock; import org.junit.After; import org.junit.Before; @@ -18,6 +19,7 @@ import java.nio.charset.CharacterCodingException; import static com.yahoo.vespa.hosted.controller.integration.AthenzFilterMock.IDENTITY_HEADER_NAME; import static com.yahoo.vespa.hosted.controller.integration.AthenzFilterMock.OKTA_ACCESS_TOKEN_HEADER_NAME; +import static com.yahoo.vespa.hosted.controller.integration.AthenzFilterMock.OKTA_IDENTITY_TOKEN_HEADER_NAME; import static org.junit.Assert.assertEquals; /** @@ -154,6 +156,11 @@ public class ControllerContainerTest { return request; } + protected static Request addOktaIdentityToken(Request request, OktaIdentityToken token) { + request.getHeaders().put(OKTA_IDENTITY_TOKEN_HEADER_NAME, token.token()); + return request; + } + protected static Request addOktaAccessToken(Request request, OktaAccessToken token) { request.getHeaders().put(OKTA_ACCESS_TOKEN_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 eac60b72c6f..f7af8ff4ce4 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 @@ -21,6 +21,7 @@ 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.OktaAccessToken; +import com.yahoo.vespa.athenz.api.OktaIdentityToken; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Instance; @@ -47,7 +48,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.resource.MockTenantCost import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint; -import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMeteringClient; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.Change; @@ -85,7 +85,6 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; -import java.time.LocalDate; import java.time.YearMonth; import java.util.ArrayList; import java.util.Base64; @@ -151,7 +150,8 @@ public class ApplicationApiTest extends ControllerContainerTest { private static final UserId USER_ID = new UserId("myuser"); private static final UserId OTHER_USER_ID = new UserId("otheruser"); private static final UserId HOSTED_VESPA_OPERATOR = new UserId("johnoperator"); - private static final OktaAccessToken OKTA_AT = new OktaAccessToken("dummy"); + private static final OktaIdentityToken OKTA_IT = new OktaIdentityToken("okta-it"); + private static final OktaAccessToken OKTA_AT = new OktaAccessToken("okta-at"); 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")); @@ -179,12 +179,12 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("tenant-without-applications.json")); // PUT (modify) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // GET the authenticated user (with associated tenants) @@ -204,11 +204,11 @@ public class ApplicationApiTest extends ControllerContainerTest { new File("tenant-list.json")); // GET list of months for a tenant - tester.assertResponse(request("/application/v4/tenant/tenant1/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT), + tester.assertResponse(request("/application/v4/tenant/tenant1/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"months\":[]}"); // GET cost for a month for a tenant - tester.assertResponse(request("/application/v4/tenant/tenant1/cost/2018-01", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT), + tester.assertResponse(request("/application/v4/tenant/tenant1/cost/2018-01", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"month\":\"2018-01\",\"items\":[]}"); // Add another Athens domain, so we can try to create more tenants @@ -219,13 +219,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (add) a tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant2", POST) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .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) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"), new File("tenant-without-applications-with-id.json")); // GET a tenant with property ID and contact information @@ -236,7 +236,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (create) an application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference.json")); // GET a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", GET).userIdentity(USER_ID), @@ -345,7 +345,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/instance/default", POST) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference-2.json")); ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default"); @@ -416,7 +416,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE application tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted application tenant2.application2\"}"); // Set version 6.1 to broken to change compile version for. @@ -577,7 +577,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE application with active deployments fails tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("delete-with-active-deployments.json"), 400); // DELETE (deactivate) a deployment - dev @@ -792,24 +792,24 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE all instances under an application to delete the application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.default\"}"); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/my-user", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.my-user\"}"); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.instance1\"}"); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.otheruser\"}"); // DELETE a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("tenant-without-applications.json")); } @@ -960,13 +960,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // Create tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("tenant-without-applications.json")); // Create application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference.json")); // Grant deploy access @@ -1081,7 +1081,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/doesnotexist/application/doesnotexist/metering", GET) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance1-metering.json")); } @@ -1099,7 +1099,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/" + applicationId.tenant().value() + "/cost", GET) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"months\":[\"2019-09\",\"2019-10\"]}"); CostInfo costInfo1 = new CostInfo(applicationId, ZoneId.from("prod", "us-south-1"), @@ -1119,7 +1119,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/" + applicationId.tenant().value() + "/cost/2019-09", GET) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("cost-report.json")); } @@ -1131,7 +1131,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // PUT (update) non-existing tenant returns 403 as tenant access cannot be determined when the tenant does not exist tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}", 403); @@ -1158,21 +1158,21 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), 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\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"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) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'tenant1' already exists\"}", 400); @@ -1181,7 +1181,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/my_tenant_2", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"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); @@ -1189,7 +1189,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/by-tenant2", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz tenant name cannot have prefix 'by-'\"}", 400); @@ -1197,19 +1197,19 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/hosted-vespa", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'hosted-vespa' already exists\"}", 400); // POST (create) an (empty) application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference.json")); // Create the same application again tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .userIdentity(USER_ID), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create 'tenant1.application1.instance1': Instance already exists\"}", 400); @@ -1263,37 +1263,37 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE tenant which has an application tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"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/instance/instance1", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.instance1\"}"); // DELETE application again - should produce 404 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE) - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .userIdentity(USER_ID), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete instance 'tenant1.application1.instance1': Instance not found\"}", 404); // GET cost of unknown tenant - tester.assertResponse(request("/application/v4/tenant/no-such-tenant/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT), + tester.assertResponse(request("/application/v4/tenant/no-such-tenant/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'no-such-tenant' does not exist\"}", 404); - tester.assertResponse(request("/application/v4/tenant/no-such-tenant/cost/2018-01-01", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT), + tester.assertResponse(request("/application/v4/tenant/no-such-tenant/cost/2018-01-01", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'no-such-tenant' does not exist\"}", 404); // GET cost with invalid date string - tester.assertResponse(request("/application/v4/tenant/tenant1/cost/not-a-valid-date", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT), + tester.assertResponse(request("/application/v4/tenant/tenant1/cost/not-a-valid-date", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not parse year-month 'not-a-valid-date'\"}", 400); // DELETE tenant tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("tenant-without-applications.json")); // DELETE tenant again returns 403 as tenant access cannot be determined when the tenant does not exist tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) @@ -1308,7 +1308,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/my-tenant", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'my-tenant' already exists\"}", 400); } @@ -1336,7 +1336,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // Creating a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT) .userIdentity(unauthorizedUser), "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'user.othertenant' is not admin in Athenz domain 'domain1'\"}", 403); @@ -1345,21 +1345,21 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), 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/instance/instance1", POST) .userIdentity(unauthorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}", 403); // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference.json"), 200); @@ -1380,28 +1380,28 @@ public class ApplicationApiTest extends ControllerContainerTest { // Create another instance under the application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default", POST) .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference-default.json"), 200); // Deleting the application when more than one instance is present is forbidden tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not delete application; more than one instance present: [tenant1.application1, tenant1.application1.instance1]\"}", 400); // Deleting one instance is OK tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default", DELETE) .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.default\"}", 200); // (Deleting the application with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted application tenant1.application1\"}", 200); @@ -1417,7 +1417,7 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain2\", \"property\":\"property1\"}") .userIdentity(authorizedUser) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"tenant\":\"tenant1\",\"type\":\"ATHENS\",\"athensDomain\":\"domain2\",\"property\":\"property1\",\"applications\":[]}", 200); @@ -1851,11 +1851,11 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("tenant-without-applications.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT), + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), new File("instance-reference.json")); addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1")); @@ -1976,6 +1976,7 @@ public class ApplicationApiTest extends ControllerContainerTest { private final Request.Method method; private byte[] data = new byte[0]; private AthenzIdentity identity; + private OktaIdentityToken oktaIdentityToken; private OktaAccessToken oktaAccessToken; private String contentType = "application/json"; private Map> headers = new HashMap<>(); @@ -1994,6 +1995,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 oktaIdentityToken(OktaIdentityToken oktaIdentityToken) { this.oktaIdentityToken = oktaIdentityToken; 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; } @@ -2014,6 +2016,9 @@ public class ApplicationApiTest extends ControllerContainerTest { if (identity != null) { addIdentityToRequest(request, identity); } + if (oktaIdentityToken != null) { + addOktaIdentityToken(request, oktaIdentityToken); + } if (oktaAccessToken != null) { addOktaAccessToken(request, oktaAccessToken); } -- cgit v1.2.3