diff options
author | Morten Tokle <morten.tokle@gmail.com> | 2017-12-05 09:40:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-05 09:40:43 +0100 |
commit | 2e399df23fd841161e5913ae154800b2db39eaae (patch) | |
tree | 6f0ce3772ab98ce4c806b47e161a765db7976693 /controller-server | |
parent | 69c0128865dac863aba8d1d8ea511eec850e2ade (diff) | |
parent | 15df8a6d11c0883045ab6bdb2330e537728faf4f (diff) |
Merge pull request #4342 from vespa-engine/bjorncs/enforce-athenz-principal
Bjorncs/enforce athenz principal
Diffstat (limited to 'controller-server')
25 files changed, 398 insertions, 279 deletions
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 9530e9a982c..1dfe92f64e1 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 @@ -13,7 +13,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; import com.yahoo.vespa.hosted.controller.athenz.ZmsException; @@ -67,7 +67,7 @@ public class TenantController { public List<Tenant> asList(UserId user) { Set<UserGroup> userGroups = entityService.getUserGroups(user); Set<AthenzDomain> userDomains = new HashSet<>(athenzClientFactory.createZtsClientWithServicePrincipal() - .getTenantDomainsForUser(AthenzUtils.createPrincipal(user))); + .getTenantDomainsForUser(AthenzUser.fromUserId(user))); Predicate<Tenant> hasUsersGroup = (tenant) -> tenant.getUserGroup().isPresent() && userGroups.contains(tenant.getUserGroup().get()); Predicate<Tenant> hasUsersDomain = (tenant) -> tenant.getAthensDomain().isPresent() && userDomains.contains(tenant.getAthensDomain().get()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzIdentity.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzIdentity.java new file mode 100644 index 00000000000..08d9b02f0e8 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzIdentity.java @@ -0,0 +1,16 @@ +// 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; + + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; + +/** + * @author bjorncs + */ +public interface AthenzIdentity { + AthenzDomain getDomain(); + String getName(); + default String getFullName() { + return getDomain().id() + "." + getName(); + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java index 1e4952a39c5..613f122b831 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import java.security.Principal; import java.util.Objects; @@ -12,36 +11,29 @@ import java.util.Objects; */ public class AthenzPrincipal implements Principal { - private final AthenzDomain domain; - private final UserId userId; + private final AthenzIdentity athenzIdentity; - public AthenzPrincipal(AthenzDomain domain, UserId userId) { - this.domain = domain; - this.userId = userId; + public AthenzPrincipal(AthenzIdentity athenzIdentity) { + this.athenzIdentity = athenzIdentity; } - public UserId getUserId() { - return userId; - } - - public AthenzDomain getDomain() { - return domain; - } - - public String toYRN() { - return domain.id() + "." + userId.id(); + public AthenzIdentity getIdentity() { + return athenzIdentity; } @Override public String getName() { - return userId.id(); + return athenzIdentity.getFullName(); + } + + public AthenzDomain getDomain() { + return athenzIdentity.getDomain(); } @Override public String toString() { return "AthenzPrincipal{" + - "domain=" + domain + - ", userId=" + userId + + "athenzIdentity=" + athenzIdentity + '}'; } @@ -49,14 +41,12 @@ public class AthenzPrincipal implements Principal { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AthenzPrincipal that = (AthenzPrincipal) o; - return Objects.equals(domain, that.domain) && - Objects.equals(userId, that.userId); + AthenzPrincipal principal = (AthenzPrincipal) o; + return Objects.equals(athenzIdentity, principal.athenzIdentity); } @Override public int hashCode() { - return Objects.hash(domain, userId); + return Objects.hash(athenzIdentity); } - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java index 37c6459b687..fbbc45e856a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java @@ -2,13 +2,14 @@ package com.yahoo.vespa.hosted.controller.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import java.util.Objects; /** * @author bjorncs */ -public class AthenzService { +public class AthenzService implements AthenzIdentity { private final AthenzDomain domain; private final String serviceName; @@ -22,15 +23,17 @@ public class AthenzService { this(new AthenzDomain(domain), serviceName); } - public String toFullServiceName() { - return domain.id() + "." + serviceName; + public static AthenzService fromScrewdriverId(ScrewdriverId screwdriverId) { + return new AthenzService(AthenzUtils.SCREWDRIVER_DOMAIN, "sd" + screwdriverId.id()); } + @Override public AthenzDomain getDomain() { return domain; } - public String getServiceName() { + @Override + public String getName() { return serviceName; } @@ -50,6 +53,6 @@ public class AthenzService { @Override public String toString() { - return String.format("AthenzService(%s)", toFullServiceName()); + return String.format("AthenzService(%s)", getFullName()); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUser.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUser.java new file mode 100644 index 00000000000..32ebc4d78b4 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUser.java @@ -0,0 +1,56 @@ +// 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; + +import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; +import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; + +import java.util.Objects; + +/** + * @author bjorncs + */ +public class AthenzUser implements AthenzIdentity { + private final UserId userId; + + public AthenzUser(UserId userId) { + this.userId = userId; + } + + public static AthenzUser fromUserId(UserId userId) { + return new AthenzUser(userId); + } + + @Override + public AthenzDomain getDomain() { + return AthenzUtils.USER_PRINCIPAL_DOMAIN; + } + + @Override + public String getName() { + return userId.id(); + } + + public UserId getUserId() { + return userId; + } + + @Override + public String toString() { + return "AthenzUser{" + + "userId=" + userId + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AthenzUser that = (AthenzUser) o; + return Objects.equals(userId, that.userId); + } + + @Override + public int hashCode() { + return Objects.hash(userId); + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java index 18bd626369d..6517d97151b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.athenz; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; /** @@ -16,13 +15,12 @@ public class AthenzUtils { public static final AthenzDomain SCREWDRIVER_DOMAIN = new AthenzDomain("cd.screwdriver.project"); public static final AthenzService ZMS_ATHENZ_SERVICE = new AthenzService("sys.auth", "zms"); - public static AthenzPrincipal createPrincipal(UserId userId) { - return new AthenzPrincipal(USER_PRINCIPAL_DOMAIN, userId); + public static AthenzIdentity createAthenzIdentity(AthenzDomain domain, String identityName) { + if (domain.equals(USER_PRINCIPAL_DOMAIN)) { + return AthenzUser.fromUserId(new UserId(identityName)); + } else { + return new AthenzService(domain, identityName); + } } - public static AthenzPrincipal createPrincipal(ScrewdriverId screwdriverId) { - return new AthenzPrincipal(SCREWDRIVER_DOMAIN, new UserId("sd" + screwdriverId.id())); - } - - } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java index 7e3abeb77d9..24ebe337e1f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.athenz; import com.yahoo.athenz.auth.token.PrincipalToken; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import java.security.PrivateKey; import java.security.PublicKey; @@ -38,11 +37,11 @@ public class NToken { } public AthenzPrincipal getPrincipal() { - return new AthenzPrincipal(getDomain(), getUser()); + return new AthenzPrincipal(getAthenzIdentity()); } - public UserId getUser() { - return new UserId(token.getName()); + public AthenzIdentity getAthenzIdentity() { + return AthenzUtils.createAthenzIdentity(getDomain(), token.getName()); } public AthenzDomain getDomain() { @@ -66,7 +65,7 @@ public class NToken { @Override public String toString() { - return String.format("NToken(%s)", getToken()); + return String.format("NToken(%s)", token.getUnsignedToken()); } @Override @@ -85,7 +84,7 @@ public class NToken { public static class Builder { private final String version; - private final AthenzPrincipal principal; + private final AthenzIdentity identity; private final PrivateKey privateKey; private final String keyId; private Optional<String> salt = Optional.empty(); @@ -99,9 +98,9 @@ public class NToken { * {@link PrincipalToken#PrincipalToken(String)} only accepts signed token * (supplying an unsigned token to the constructor will result in inconsistent state) */ - public Builder(String version, AthenzPrincipal principal, PrivateKey privateKey, String keyId) { + public Builder(String version, AthenzIdentity identity, PrivateKey privateKey, String keyId) { this.version = version; - this.principal = principal; + this.identity = identity; this.privateKey = privateKey; this.keyId = keyId; } @@ -132,7 +131,7 @@ public class NToken { } public NToken build() { - PrincipalToken token = new PrincipalToken.Builder(version, principal.getDomain().id(), principal.getName()) + PrincipalToken token = new PrincipalToken.Builder(version, identity.getDomain().id(), identity.getName()) .keyId(this.keyId) .salt(this.salt.orElse(null)) .host(this.hostname.orElse(null)) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java index 407bce05c6e..09673721f9d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java @@ -19,12 +19,12 @@ public interface ZmsClient { void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName); - boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName); + boolean hasApplicationAccess(AthenzIdentity athenzIdentity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName); - boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain); + boolean hasTenantAdminAccess(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain); // Used before vespa tenancy is established for the domain. - boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain); + boolean isDomainAdmin(AthenzIdentity athenzIdentity, AthenzDomain domain); List<AthenzDomain> getDomainList(String prefix); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java index f400ba2eb99..5db3c55e9ce 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java @@ -10,6 +10,6 @@ import java.util.List; */ public interface ZtsClient { - List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal); + List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity principal); } 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 index bfa543f160a..d4a5b691350 100644 --- 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 @@ -9,7 +9,7 @@ import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; import com.yahoo.vespa.hosted.controller.restapi.application.Authorizer; @@ -85,9 +85,9 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter { Principal userPrincipal = request.getUserPrincipal(); log.log(LogLevel.DEBUG, () -> "Original user principal: " + userPrincipal.toString()); UserId userId = new UserId(userPrincipal.getName()); - AthenzPrincipal athenzPrincipal = AthenzUtils.createPrincipal(userId); - request.setUserPrincipal(athenzPrincipal); - request.setRemoteUser(athenzPrincipal.toYRN()); + AthenzUser athenzIdentity = AthenzUser.fromUserId(userId); + request.setRemoteUser(athenzIdentity.getFullName()); + request.setUserPrincipal(new AthenzPrincipal(athenzIdentity)); } private enum UserAuthenticationResult { 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 index 110e06b767c..ee488914aa9 100644 --- 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 @@ -14,7 +14,7 @@ import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; import com.yahoo.vespa.hosted.controller.athenz.AthenzService; import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; @@ -49,16 +49,16 @@ public class ZmsClientImpl implements ZmsClient { runOrThrow(() -> { Tenancy tenancy = new Tenancy() .setDomain(tenantDomain.id()) - .setService(service.toFullServiceName()) + .setService(service.getFullName()) .setResourceGroups(Collections.emptyList()); - zmsClient.putTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null, tenancy); + zmsClient.putTenancy(tenantDomain.id(), service.getFullName(), /*auditref*/null, tenancy); }); } @Override public void deleteTenant(AthenzDomain tenantDomain) { log("deleteTenancy(tenantDomain=%s, service=%s)", tenantDomain, service); - runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.toFullServiceName(), /*auditref*/null)); + runOrThrow(() -> zmsClient.deleteTenancy(tenantDomain.id(), service.getFullName(), /*auditref*/null)); } @Override @@ -66,16 +66,16 @@ public class ZmsClientImpl implements ZmsClient { List<TenantRoleAction> tenantRoleActions = createTenantRoleActions(); log("putProviderResourceGroupRoles(" + "tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s, roleActions=%s)", - tenantDomain, service.getDomain().id(), service.getServiceName(), applicationName, tenantRoleActions); + tenantDomain, service.getDomain().id(), service.getFullName(), applicationName, tenantRoleActions); runOrThrow(() -> { ProviderResourceGroupRoles resourceGroupRoles = new ProviderResourceGroupRoles() .setDomain(service.getDomain().id()) - .setService(service.getServiceName()) + .setService(service.getFullName()) .setTenant(tenantDomain.id()) .setResourceGroup(applicationName.id()) .setRoles(tenantRoleActions); zmsClient.putProviderResourceGroupRoles( - tenantDomain.id(), service.getDomain().id(), service.getServiceName(), + tenantDomain.id(), service.getDomain().id(), service.getName(), applicationName.id(), /*auditref*/null, resourceGroupRoles); }); } @@ -83,34 +83,34 @@ public class ZmsClientImpl implements ZmsClient { @Override public void deleteApplication(AthenzDomain tenantDomain, ApplicationId applicationName) { log("deleteProviderResourceGroupRoles(tenantDomain=%s, providerDomain=%s, service=%s, resourceGroup=%s)", - tenantDomain, service.getDomain().id(), service.getServiceName(), applicationName); + tenantDomain, service.getDomain().id(), service.getName(), applicationName); runOrThrow(() -> { zmsClient.deleteProviderResourceGroupRoles( - tenantDomain.id(), service.getDomain().id(), service.getServiceName(), applicationName.id(), /*auditref*/null); + tenantDomain.id(), service.getDomain().id(), service.getName(), applicationName.id(), /*auditref*/null); }); } @Override public boolean hasApplicationAccess( - AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { + AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { return hasAccess( - action.name(), applicationResourceString(tenantDomain, applicationName), principal); + action.name(), applicationResourceString(tenantDomain, applicationName), identity); } @Override - public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain) { - return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), principal); + public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) { + return hasAccess(TenantAction._modify_.name(), tenantResourceString(tenantDomain), identity); } /** * Used when creating tenancies. As there are no tenancy policies at this point, - * we cannot use {@link #hasTenantAdminAccess(AthenzPrincipal, AthenzDomain)} + * we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)} */ @Override - public boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain) { - log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", principal); + public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) { + log("getMembership(domain=%s, role=%s, principal=%s)", domain, "admin", identity); return getOrThrow( - () -> zmsClient.getMembership(domain.id(), "admin", principal.toYRN()).getIsMember()); + () -> zmsClient.getMembership(domain.id(), "admin", identity.getFullName()).getIsMember()); } @Override @@ -127,18 +127,18 @@ public class ZmsClientImpl implements ZmsClient { @Override public AthenzPublicKey getPublicKey(AthenzService service, String keyId) { - log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getServiceName(), keyId); + log("getPublicKeyEntry(domain=%s, service=%s, keyId=%s)", service.getDomain().id(), service.getName(), keyId); return getOrThrow(() -> { - PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getServiceName(), keyId); + PublicKeyEntry entry = zmsClient.getPublicKeyEntry(service.getDomain().id(), service.getName(), keyId); return fromYbase64EncodedKey(entry.getKey(), keyId); }); } @Override public List<AthenzPublicKey> getPublicKeys(AthenzService service) { - log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getServiceName()); + log("getServiceIdentity(domain=%s, service=%s)", service.getDomain().id(), service.getName()); return getOrThrow(() -> { - ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getServiceName()); + ServiceIdentity serviceIdentity = zmsClient.getServiceIdentity(service.getDomain().id(), service.getName()); return toAthenzPublicKeys(serviceIdentity.getPublicKeys()); }); } @@ -163,10 +163,11 @@ public class ZmsClientImpl implements ZmsClient { .collect(toList()); } - private boolean hasAccess(String action, String resource, AthenzPrincipal principal) { - log("getAccess(action=%s, resource=%s, principal=%s)", action, resource, principal); + 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, principal.toYRN()).getGranted()); + () -> zmsClient.getAccess(action, resource, /*trustDomain*/null, identity.getFullName()) + .getGranted()); } private static void log(String format, Object... args) { @@ -197,7 +198,7 @@ public class ZmsClientImpl implements ZmsClient { private String resourceStringPrefix(AthenzDomain tenantDomain) { return String.format("%s:service.%s.tenant.%s", - service.getDomain().id(), service.getServiceName(), tenantDomain.id()); + service.getDomain().id(), service.getName(), tenantDomain.id()); } private String tenantResourceString(AthenzDomain tenantDomain) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java index 1111e56c742..b141d728afd 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java @@ -6,7 +6,7 @@ import com.yahoo.athenz.zts.ZTSClient; import com.yahoo.athenz.zts.ZTSClientException; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; import com.yahoo.vespa.hosted.controller.athenz.AthenzService; import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; import com.yahoo.vespa.hosted.controller.athenz.ZtsException; @@ -33,13 +33,13 @@ public class ZtsClientImpl implements ZtsClient { } @Override - public List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal) { + public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) { log.log(LogLevel.DEBUG, String.format( - "getTenantDomains(domain=%s, username=%s, rolename=admin, service=%s)", - service.getDomain().id(), principal, service.getServiceName())); + "getTenantDomains(domain=%s, identity=%s, rolename=admin, service=%s)", + service.getDomain().id(), identity.getFullName(), service.getFullName())); try { TenantDomains domains = ztsClient.getTenantDomains( - service.getDomain().id(), principal.toYRN(), "admin", service.getServiceName()); + service.getDomain().id(), identity.getFullName(), "admin", service.getName()); return domains.getTenantDomainNames().stream() .map(AthenzDomain::new) .collect(toList()); 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 017e8c7be44..b6dc8aa7e25 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.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; import java.util.HashMap; import java.util.HashSet; @@ -26,8 +26,8 @@ public class AthenzDbMock { public static class Domain { public final AthenzDomain name; - public final Set<AthenzPrincipal> admins = new HashSet<>(); - public final Set<AthenzPrincipal> tenantAdmins = new HashSet<>(); + public final Set<AthenzIdentity> admins = new HashSet<>(); + public final Set<AthenzIdentity> tenantAdmins = new HashSet<>(); public final Map<ApplicationId, Application> applications = new HashMap<>(); public boolean isVespaTenant = false; @@ -35,13 +35,13 @@ public class AthenzDbMock { this.name = name; } - public Domain admin(AthenzPrincipal user) { - admins.add(user); + public Domain admin(AthenzIdentity identity) { + admins.add(identity); return this; } - public Domain tenantAdmin(AthenzPrincipal user) { - tenantAdmins.add(user); + public Domain tenantAdmin(AthenzIdentity identity) { + tenantAdmins.add(identity); return this; } @@ -56,7 +56,7 @@ public class AthenzDbMock { public static class Application { - public final Map<ApplicationAction, Set<AthenzPrincipal>> acl = new HashMap<>(); + public final Map<ApplicationAction, Set<AthenzIdentity>> acl = new HashMap<>(); public Application() { acl.put(ApplicationAction.deploy, new HashSet<>()); @@ -64,8 +64,8 @@ public class AthenzDbMock { acl.put(ApplicationAction.write, new HashSet<>()); } - public Application addRoleMember(ApplicationAction action, AthenzPrincipal user) { - acl.get(action).add(user); + public Application addRoleMember(ApplicationAction action, AthenzIdentity identity) { + acl.get(action).add(identity); return this; } } 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 b2e657eae09..1f37edf5526 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 @@ -5,7 +5,7 @@ import com.yahoo.athenz.zms.ZMSClientException; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; import com.yahoo.vespa.hosted.controller.athenz.AthenzPublicKey; import com.yahoo.vespa.hosted.controller.athenz.AthenzService; import com.yahoo.vespa.hosted.controller.athenz.ZmsClient; @@ -61,28 +61,28 @@ public class ZmsClientMock implements ZmsClient { } @Override - public boolean hasApplicationAccess(AthenzPrincipal principal, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { + public boolean hasApplicationAccess(AthenzIdentity identity, ApplicationAction action, AthenzDomain tenantDomain, ApplicationId applicationName) { log("hasApplicationAccess(principal='%s', action='%s', tenantDomain='%s', applicationName='%s')", - principal, action, tenantDomain, applicationName); + 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); } - return domain.admins.contains(principal) || application.acl.get(action).contains(principal); + return domain.admins.contains(identity) || application.acl.get(action).contains(identity); } @Override - public boolean hasTenantAdminAccess(AthenzPrincipal principal, AthenzDomain tenantDomain) { - log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", principal, tenantDomain); - return isDomainAdmin(principal, tenantDomain) || - getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(principal); + public boolean hasTenantAdminAccess(AthenzIdentity identity, AthenzDomain tenantDomain) { + log("hasTenantAdminAccess(principal='%s', tenantDomain='%s')", identity, tenantDomain); + return isDomainAdmin(identity, tenantDomain) || + getDomainOrThrow(tenantDomain, true).tenantAdmins.contains(identity); } @Override - public boolean isDomainAdmin(AthenzPrincipal principal, AthenzDomain domain) { - log("isDomainAdmin(principal='%s', domain='%s')", principal, domain); - return getDomainOrThrow(domain, false).admins.contains(principal); + public boolean isDomainAdmin(AthenzIdentity identity, AthenzDomain domain) { + log("isDomainAdmin(principal='%s', domain='%s')", identity, domain); + return getDomainOrThrow(domain, false).admins.contains(identity); } @Override diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java index f21bc011273..ee983d851ce 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.controller.athenz.mock; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; import com.yahoo.vespa.hosted.controller.athenz.ZtsClient; import java.util.List; @@ -24,10 +24,10 @@ public class ZtsClientMock implements ZtsClient { } @Override - public List<AthenzDomain> getTenantDomainsForUser(AthenzPrincipal principal) { - log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", principal); + public List<AthenzDomain> getTenantDomainsForUser(AthenzIdentity identity) { + log.log(Level.INFO, "getTenantDomainsForUser(principal='%s')", identity); return athenz.domains.values().stream() - .filter(domain -> domain.tenantAdmins.contains(principal) || domain.admins.contains(principal)) + .filter(domain -> domain.tenantAdmins.contains(identity) || domain.admins.contains(identity)) .map(domain -> domain.name) .collect(toList()); } 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 947027710c8..432e1a9cc40 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 @@ -65,6 +65,9 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; @@ -238,7 +241,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler { String userIdString = request.getProperty("userOverride"); if (userIdString == null) userIdString = userFrom(request) - .orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride")); + .map(UserId::id) + .orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride")); UserId userId = new UserId(userIdString); List<Tenant> tenants = controller.tenants().asList(userId); @@ -503,7 +507,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler { Inspector requestData = toSlime(request.getData()).get(); String reason = mandatory("reason", requestData).asString(); - String agent = authorizer.getUserId(request).toString(); + String agent = authorizer.getIdentity(request).getFullName(); long timestamp = controller.clock().instant().getEpochSecond(); EndpointStatus.Status status = inService ? EndpointStatus.Status.in : EndpointStatus.Status.out; EndpointStatus endPointStatus = new EndpointStatus(status, reason, agent, timestamp); @@ -592,15 +596,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private HttpResponse createUser(HttpRequest request) { - Optional<String> username = userFrom(request); - if ( ! username.isPresent() ) throw new ForbiddenException("Not authenticated."); + Optional<UserId> user = userFrom(request); + if ( ! user.isPresent() ) throw new ForbiddenException("Not authenticated."); try { - controller.tenants().createUserTenant(username.get()); - return new MessageResponse("Created user '" + username.get() + "'"); + controller.tenants().createUserTenant(user.get().id()); + return new MessageResponse("Created user '" + user.get() + "'"); } catch (AlreadyExistsException e) { // Ok - return new MessageResponse("User '" + username + "' already exists"); + return new MessageResponse("User '" + user + "' already exists"); } } @@ -868,8 +872,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } } - private Optional<String> userFrom(HttpRequest request) { - return authorizer.getPrincipalIfAny(request).map(Principal::getName); + private Optional<UserId> userFrom(HttpRequest request) { + return authorizer.getPrincipalIfAny(request) + .map(AthenzPrincipal::getIdentity) + .filter(AthenzUser.class::isInstance) + .map(AthenzUser.class::cast) + .map(AthenzUser::getUserId); } private void toSlime(Cursor object, Tenant tenant, HttpRequest request, boolean listApplications) { @@ -980,18 +988,22 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private void throwIfNotSuperUserOrPartOfOpsDbGroup(UserGroup userGroup, HttpRequest request) { - UserId userId = authorizer.getUserId(request); - if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(userId, userGroup) ) { + AthenzIdentity identity = authorizer.getIdentity(request); + if (!(identity instanceof AthenzUser)) { + throw new ForbiddenException("Identity not an user: " + identity.getFullName()); + } + AthenzUser user = (AthenzUser) identity; + if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(user.getUserId(), userGroup) ) { throw new ForbiddenException(String.format("User '%s' is not super user or part of the OpsDB user group '%s'", - userId.id(), userGroup.id())); + user.getUserId().id(), userGroup.id())); } } private void throwIfNotAthenzDomainAdmin(AthenzDomain tenantDomain, HttpRequest request) { - UserId userId = authorizer.getUserId(request); - if ( ! authorizer.isAthenzDomainAdmin(userId, tenantDomain)) { + AthenzIdentity identity = authorizer.getIdentity(request); + if ( ! authorizer.isAthenzDomainAdmin(identity, tenantDomain)) { throw new ForbiddenException( - String.format("The user '%s' is not admin in Athenz domain '%s'", userId.id(), tenantDomain.id())); + String.format("The user '%s' is not admin in Athenz domain '%s'", identity.getFullName(), tenantDomain.id())); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java index 0c808e30c2a..9e1ccbdcac1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java @@ -11,7 +11,9 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.common.ContextAttributes; import com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter; @@ -19,7 +21,6 @@ import com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter; import javax.ws.rs.ForbiddenException; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.SecurityContext; -import java.security.Principal; import java.util.Optional; import java.util.logging.Logger; @@ -54,27 +55,26 @@ public class Authorizer { Optional<Tenant> tenant = controller.tenants().tenant(tenantId); if ( ! tenant.isPresent()) return; - UserId userId = getUserId(request); - if (isTenantAdmin(userId, tenant.get())) return; + AthenzIdentity identity = getIdentity(request); + if (isTenantAdmin(identity, tenant.get())) return; - throw loggedForbiddenException("User " + userId + " does not have write access to tenant " + tenantId); + throw loggedForbiddenException("User " + identity.getFullName() + " does not have write access to tenant " + tenantId); } - public UserId getUserId(HttpRequest request) { - String name = getPrincipal(request).getName(); - if (name == null) - throw loggedForbiddenException("Not authorized: User name is null"); - return new UserId(name); + public AthenzIdentity getIdentity(HttpRequest request) { + return getPrincipal(request).getIdentity(); } /** Returns the principal or throws forbidden */ // TODO: Avoid REST exceptions - public Principal getPrincipal(HttpRequest request) { + public AthenzPrincipal getPrincipal(HttpRequest request) { return getPrincipalIfAny(request).orElseThrow(() -> Authorizer.loggedForbiddenException("User is not authenticated")); } /** Returns the principal if there is any */ - public Optional<Principal> getPrincipalIfAny(HttpRequest request) { - return securityContextOf(request).map(SecurityContext::getUserPrincipal); + public Optional<AthenzPrincipal> getPrincipalIfAny(HttpRequest request) { + return securityContextOf(request) + .map(SecurityContext::getUserPrincipal) + .map(AthenzPrincipal.class::cast); } public Optional<NToken> getNToken(HttpRequest request) { @@ -93,26 +93,36 @@ public class Authorizer { return new ForbiddenException(formattedMessage); } - private boolean isTenantAdmin(UserId userId, Tenant tenant) { + private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) { switch (tenant.tenantType()) { case ATHENS: - return isAthenzTenantAdmin(userId, tenant.getAthensDomain().get()); - case OPSDB: - return isGroupMember(userId, tenant.getUserGroup().get()); - case USER: - return isUserTenantOwner(tenant.getId(), userId); + return isAthenzTenantAdmin(identity, tenant.getAthensDomain().get()); + case OPSDB: { + if (!(identity instanceof AthenzUser)) { + return false; + } + AthenzUser user = (AthenzUser) identity; + return isGroupMember(user.getUserId(), tenant.getUserGroup().get()); + } + case USER: { + if (!(identity instanceof AthenzUser)) { + return false; + } + AthenzUser user = (AthenzUser) identity; + return isUserTenantOwner(tenant.getId(), user.getUserId()); + } } throw new IllegalArgumentException("Unknown tenant type: " + tenant.tenantType()); } - private boolean isAthenzTenantAdmin(UserId userId, AthenzDomain tenantDomain) { + private boolean isAthenzTenantAdmin(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain) { return athenzClientFactory.createZmsClientWithServicePrincipal() - .hasTenantAdminAccess(AthenzUtils.createPrincipal(userId), tenantDomain); + .hasTenantAdminAccess(athenzIdentity, tenantDomain); } - public boolean isAthenzDomainAdmin(UserId userId, AthenzDomain tenantDomain) { + public boolean isAthenzDomainAdmin(AthenzIdentity identity, AthenzDomain tenantDomain) { return athenzClientFactory.createZmsClientWithServicePrincipal() - .isDomainAdmin(AthenzUtils.createPrincipal(userId), tenantDomain); + .isDomainAdmin(identity, tenantDomain); } public boolean isGroupMember(UserId userId, UserGroup userGroup) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java index 2e627676766..8b548d96f62 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java @@ -86,7 +86,7 @@ public class DeployAuthorizer { "Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " + "Either the application has not been created at " + zoneRegistry.getDashboardUri() + " or " + "'%1$s' is not added to the application's deployer role in Athenz domain '%3$s'.", - athenzPrincipal.toYRN(), applicationId, tenantDomain.id()); + athenzPrincipal.getIdentity().getFullName(), applicationId, tenantDomain.id()); } } } @@ -107,7 +107,7 @@ public class DeployAuthorizer { try { return athenzClientFactory.createZmsClientWithServicePrincipal() .hasApplicationAccess( - principal, + principal.getIdentity(), ApplicationAction.deploy, domain, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(applicationId.application().value())); 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 index 355b63335c0..9dbebecdb25 100644 --- 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 @@ -9,7 +9,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.RegionId; import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.filter.AthenzTestUtils; @@ -35,7 +35,7 @@ public class TestIdentities { public static Tenant tenant = Tenant.createOpsDbTenant(tenantId, userGroup1, property); public static NToken userNToken = new NToken.Builder( - "U1", AthenzUtils.createPrincipal(userId), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") + "U1", AthenzUser.fromUserId(userId), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") .build(); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java index 20db038485d..5a3f048101a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java @@ -8,7 +8,7 @@ import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; import com.yahoo.vespa.hosted.controller.athenz.NToken; import org.junit.Before; @@ -44,7 +44,7 @@ public class AthenzPrincipalFilterTest { @Before public void before() { validator = mock(NTokenValidator.class); - principal = AthenzUtils.createPrincipal(new UserId("bob")); + principal = new AthenzPrincipal(AthenzUser.fromUserId(new UserId("bob"))); } @Test @@ -94,7 +94,7 @@ public class AthenzPrincipalFilterTest { private static NToken createDummyToken() { return new NToken.Builder( - "U1", AthenzUtils.createPrincipal(new UserId("bob")), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") + "U1", AthenzUser.fromUserId(new UserId("bob")), AthenzTestUtils.generateRsaKeypair().getPrivate(), "0") .build(); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java index 70ba504df03..927910a89a1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java @@ -2,7 +2,9 @@ package com.yahoo.vespa.hosted.controller.athenz.filter; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; @@ -15,7 +17,6 @@ import java.security.NoSuchAlgorithmException; import java.util.Optional; import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.ZMS_ATHENZ_SERVICE; -import static com.yahoo.vespa.hosted.controller.athenz.AthenzUtils.createPrincipal; import static org.junit.Assert.assertEquals; /** @@ -25,7 +26,7 @@ public class NTokenValidatorTest { private static final KeyPair TRUSTED_KEY = AthenzTestUtils.generateRsaKeypair(); private static final KeyPair UNKNOWN_KEY = AthenzTestUtils.generateRsaKeypair(); - private static final AthenzPrincipal PRINCIPAL = createPrincipal(new UserId("myuser")); + private static final AthenzIdentity IDENTITY = AthenzUser.fromUserId(new UserId("myuser")); @Rule public ExpectedException exceptionRule = ExpectedException.none(); @@ -33,15 +34,15 @@ public class NTokenValidatorTest { @Test public void valid_token_is_accepted() throws NoSuchAlgorithmException, InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0"); + NToken token = createNToken(IDENTITY, System.currentTimeMillis(), TRUSTED_KEY, "0"); AthenzPrincipal principal = validator.validate(token); - assertEquals("user.myuser", principal.toYRN()); + assertEquals("user.myuser", principal.getIdentity().getFullName()); } @Test public void invalid_signature_is_not_accepted() throws InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), UNKNOWN_KEY, "0"); + NToken token = createNToken(IDENTITY, System.currentTimeMillis(), UNKNOWN_KEY, "0"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("NToken is expired or has invalid signature"); validator.validate(token); @@ -50,7 +51,7 @@ public class NTokenValidatorTest { @Test public void expired_token_is_not_accepted() throws InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, 1234 /*long time ago*/, TRUSTED_KEY, "0"); + NToken token = createNToken(IDENTITY, 1234 /*long time ago*/, TRUSTED_KEY, "0"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("NToken is expired or has invalid signature"); validator.validate(token); @@ -59,7 +60,7 @@ public class NTokenValidatorTest { @Test public void unknown_keyId_is_not_accepted() throws InvalidTokenException { NTokenValidator validator = new NTokenValidator(createKeystore()); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "unknown-key-id"); + NToken token = createNToken(IDENTITY, System.currentTimeMillis(), TRUSTED_KEY, "unknown-key-id"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("NToken has an unknown keyId"); validator.validate(token); @@ -69,7 +70,7 @@ public class NTokenValidatorTest { public void failing_to_find_key_should_throw_exception() throws InvalidTokenException { ZmsKeystore keystore = (athensService, keyId) -> { throw new RuntimeException(); }; NTokenValidator validator = new NTokenValidator(keystore); - NToken token = createNToken(PRINCIPAL, System.currentTimeMillis(), TRUSTED_KEY, "0"); + NToken token = createNToken(IDENTITY, System.currentTimeMillis(), TRUSTED_KEY, "0"); exceptionRule.expect(InvalidTokenException.class); exceptionRule.expectMessage("Failed to retrieve public key"); validator.validate(token); @@ -82,8 +83,8 @@ public class NTokenValidatorTest { : Optional.empty(); } - private static NToken createNToken(AthenzPrincipal principal, long issueTime, KeyPair keyPair, String keyId) { - return new NToken.Builder("U1", principal, keyPair.getPrivate(), keyId) + private static NToken createNToken(AthenzIdentity identity, long issueTime, KeyPair keyPair, String keyId) { + return new NToken.Builder("U1", identity, keyPair.getPrivate(), keyId) .salt("1234") .hostname("host") .ip("1.2.3.4") 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 71ad6560126..fb0adcd3152 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 @@ -20,11 +20,10 @@ 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.identifiers.TenantId; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzService; import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; @@ -108,7 +107,7 @@ public class ContainerControllerTester { AthenzDomain athensDomain = new AthenzDomain(domainName); AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain); domain.markAsVespaTenant(); - domain.admin(new AthenzPrincipal(new AthenzDomain("domain"), new UserId(userName))); + domain.admin(AthenzUtils.createAthenzIdentity(new AthenzDomain("domain"), userName)); mock.getSetup().addDomain(domain); return athensDomain; } @@ -133,6 +132,6 @@ public class ContainerControllerTester { mock.getSetup() .domains.get(tenantDomain) .applications.get(new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(application.id().application().value())) - .addRoleMember(action, AthenzUtils.createPrincipal(screwdriverId)); + .addRoleMember(action, AthenzService.fromScrewdriverId(screwdriverId)); } } 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 540550e871d..7b05f0ce438 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 @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.restapi.application; import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.hosted.controller.Application; @@ -24,8 +23,9 @@ import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; -import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; +import com.yahoo.vespa.hosted.controller.athenz.AthenzIdentity; +import com.yahoo.vespa.hosted.controller.athenz.AthenzService; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUser; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; @@ -60,6 +60,7 @@ import static com.yahoo.application.container.handler.Request.Method.PUT; /** * @author bratseth * @author mpolden + * @author bjorncs */ public class ApplicationApiTest extends ControllerContainerTest { @@ -73,9 +74,9 @@ public class ApplicationApiTest extends ControllerContainerTest { .region("us-west-1") .build(); - private static final String athenzUserDomain = "domain1"; - - private static final String athenzScrewdriverDomain = AthenzUtils.SCREWDRIVER_DOMAIN.id(); + private static final AthenzDomain ATHENZ_TENANT_DOMAIN = new AthenzDomain("domain1"); + private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345"); + private static final UserId USER_ID = new UserId("myuser"); @Test public void testApplicationApi() throws Exception { @@ -83,7 +84,7 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerTester tester = controllerTester.containerTester(); tester.updateSystemVersion(); - addTenantAthenzDomain(athenzUserDomain, "mytenant"); // (Necessary but not provided in this API) + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // (Necessary but not provided in this API) // GET API root tester.assertResponse(request("/application/v4/", GET), @@ -99,14 +100,16 @@ public class ApplicationApiTest extends ControllerContainerTest { new File("cookiefreshness.json")); // POST (add) a tenant without property ID tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // PUT (modify) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // GET the authenticated user (with associated tenants) - tester.assertResponse(request("/application/v4/user", GET), + tester.assertResponse(request("/application/v4/user", GET).userIdentity(USER_ID), new File("user.json")); // GET all tenants tester.assertResponse(request("/application/v4/tenant/", GET), @@ -114,15 +117,17 @@ public class ApplicationApiTest extends ControllerContainerTest { // Add another Athens domain, so we can try to create more tenants - addTenantAthenzDomain("domain2", "mytenant"); // New domain to test tenant w/property ID + createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); // New domain to test tenant w/property ID // Add property info for that property id, as well, in the mock organization. addPropertyData((MockOrganization) controllerTester.controller().organization(), "1234"); // POST (add) a tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant2", POST) + .userIdentity(USER_ID) .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) .data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"), new File("tenant-without-applications-with-id.json")); // GET a tenant with property ID @@ -132,15 +137,18 @@ public class ApplicationApiTest extends ControllerContainerTest { // Test legacy OpsDB tenants // POST (add) an OpsDB tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant3", POST) + .userIdentity(USER_ID) .data("{\"userGroup\":\"group1\",\"property\":\"property1\",\"propertyId\":\"1234\"}"), new File("opsdb-tenant-with-id-without-applications.json")); // PUT (modify) the OpsDB tenant to set another property tester.assertResponse(request("/application/v4/tenant/tenant3", PUT) + .userIdentity(USER_ID) .data("{\"userGroup\":\"group1\",\"property\":\"property2\",\"propertyId\":\"4321\"}"), new File("opsdb-tenant-with-new-id-without-applications.json")); // POST (create) an application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); // GET a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", GET), @@ -151,11 +159,13 @@ public class ApplicationApiTest extends ControllerContainerTest { new File("application-list.json")); // POST triggering of a full deployment to an application (if version is omitted, current system version is used) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST) + .userIdentity(USER_ID) .data("6.1.0"), new File("application-deployment.json")); // DELETE (cancel) ongoing change - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE) + .userIdentity(USER_ID), new File("application-deployment-cancelled.json")); // DELETE (cancel) again is a no-op @@ -166,14 +176,16 @@ public class ApplicationApiTest extends ControllerContainerTest { HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-result.json")); // POST (deploy) an application to a zone. This simulates calls done by our tenant pipeline. ApplicationId id = ApplicationId.from("tenant1", "application1", "default"); long screwdriverProjectId = 123; - addScrewdriverUserToDomain("screwdriveruser1", "domain1"); // (Necessary but not provided in this API) + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, + ATHENZ_TENANT_DOMAIN, + new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); // (Necessary but not provided in this API) // Trigger deployment tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST) @@ -183,7 +195,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... systemtest tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default", DELETE), "Deactivated tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default"); @@ -192,7 +204,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... staging tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default", DELETE), "Deactivated tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default"); @@ -201,7 +213,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... prod zone tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, screwdriverProjectId, false, DeploymentJobs.JobType.productionCorpUsEast1); @@ -219,22 +231,22 @@ public class ApplicationApiTest extends ControllerContainerTest { addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "default")); // GET at root, with "&recursive=deployment", returns info about all tenants, their applications and their deployments tester.assertResponse(request("/application/v4/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("deployment"), new File("recursive-root.json")); // GET at root, with "&recursive=tenant", returns info about all tenants, with limmited info about their applications. tester.assertResponse(request("/application/v4/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("tenant"), new File("recursive-until-tenant-root.json")); // GET at a tenant, with "&recursive=true", returns full info about their applications and their deployments tester.assertResponse(request("/application/v4/tenant/tenant1/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("true"), new File("tenant1-recursive.json")); // GET at an application, with "&recursive=true", returns full info about its deployments tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/", GET) - .domain("domain1").user("mytenant") + .userIdentity(USER_ID) .recursive("true"), new File("application1-recursive.json")); @@ -272,7 +284,7 @@ public class ApplicationApiTest extends ControllerContainerTest { byte[] data = new byte[0]; tester.assertResponse(request("/application/v4/user?user=newuser&domain=by", PUT) .data(data) - .domain(athenzUserDomain).user("newuser"), + .userIdentity(new UserId("newuser")), new File("create-user-response.json")); // OPTIONS return 200 OK tester.assertResponse(request("/application/v4/", Request.Method.OPTIONS), @@ -288,11 +300,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // SET global rotation override status tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation/override", PUT) + .userIdentity(USER_ID) .data("{\"reason\":\"because i can\"}"), new File("global-rotation-put.json")); // DELETE global rotation override status tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/environment/prod/region/us-west-1/instance/default/global-rotation/override", DELETE) + .userIdentity(USER_ID) .data("{\"reason\":\"because i can\"}"), new File("global-rotation-delete.json")); @@ -302,10 +316,10 @@ public class ApplicationApiTest extends ControllerContainerTest { "{\"message\":\"Successfully copied environment hosted-instance_tenant1_application1_placeholder_component_default to hosted-instance_tenant1_application1_us-west-1_prod_default\"}"); // DELETE an application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID), ""); // DELETE a tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID), new File("tenant-without-applications.json")); controllerTester.controller().deconstruct(); @@ -324,23 +338,28 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); tester.updateSystemVersion(); - addTenantAthenzDomain(athenzUserDomain, "mytenant"); - addScrewdriverUserToDomain("screwdriveruser1", "domain1"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // Create tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // Create application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); + // Grant deploy access + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, + ATHENZ_TENANT_DOMAIN, + new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1")); + // POST (deploy) an application to a prod zone - allowed when project ID is not specified HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/deploy", POST) .data(entity) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); } @@ -350,18 +369,22 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); tester.updateSystemVersion(); - addTenantAthenzDomain(athenzUserDomain, "mytenant"); - addScrewdriverUserToDomain("screwdriveruser1", "domain1"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // Create tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // Create application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); + // Give Screwdriver project deploy access + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, ATHENZ_TENANT_DOMAIN, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId("application1")); + // Deploy ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .region("us-east-3") @@ -375,7 +398,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // us-east-3 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3); @@ -392,13 +415,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // us-west-1 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsWest1); // us-east-3 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east-3/instance/default/deploy", POST) - .data(deployData).domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .data(deployData).screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3); @@ -411,7 +434,7 @@ public class ApplicationApiTest extends ControllerContainerTest { public void testErrorResponses() throws Exception { ContainerTester tester = new ContainerTester(container, responseFiles); tester.updateSystemVersion(); - addTenantAthenzDomain("domain1", "mytenant"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // PUT (update) non-existing tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) @@ -436,27 +459,32 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (add) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST) + .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), 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\"}"), "{\"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) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Tenant 'tenant1' already exists\"}", 400); // POST (create) an (empty) application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), new File("application-reference.json")); // Create the same application again - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) + .userIdentity(USER_ID), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"An application with id 'tenant1.application1' already exists\"}", 400); @@ -467,44 +495,48 @@ public class ApplicationApiTest extends ControllerContainerTest { HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-failure.json"), 400); // POST (deploy) an application without available capacity configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to prepare application", ConfigServerException.ErrorCode.OUT_OF_CAPACITY, null)); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-out-of-capacity.json"), 400); // POST (deploy) an application where activation fails configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Failed to activate application", ConfigServerException.ErrorCode.ACTIVATION_CONFLICT, null)); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-activation-conflict.json"), 409); // POST (deploy) an application where we get an internal server error configServer.throwOnNextPrepare(new ConfigServerException(new URI("server-url"), "Internal server error", ConfigServerException.ErrorCode.INTERNAL_SERVER_ERROR, null)); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), + .userIdentity(USER_ID), new File("deploy-internal-server-error.json"), 500); // DELETE tenant which has an application - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) + .userIdentity(USER_ID), "{\"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), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) + .userIdentity(USER_ID), ""); // DELETE application again - should produce 404 - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) + .userIdentity(USER_ID), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete application 'tenant1.application1': Application not found\"}", 404); // DELETE tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) + .userIdentity(USER_ID), new File("tenant-without-applications.json")); // DELETE tenant again - should produce 404 tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), @@ -520,48 +552,46 @@ public class ApplicationApiTest extends ControllerContainerTest { @Test public void testAuthorization() throws Exception { ContainerTester tester = new ContainerTester(container, responseFiles); - String authorizedUser = "mytenant"; - String unauthorizedUser = "othertenant"; + UserId authorizedUser = USER_ID; + UserId unauthorizedUser = new UserId("othertenant"); // Mutation without an authorized user is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", POST) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(null), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"FORBIDDEN\",\"message\":\"User is not authenticated\"}", 403); // ... but read methods are allowed tester.assertResponse(request("/application/v4/tenant/", GET) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(null), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "[]", 200); - addTenantAthenzDomain("domain1", "mytenant"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // 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\"}") - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'othertenant' is not admin in Athenz domain 'domain1'\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"The user 'user.othertenant' is not admin in Athenz domain 'domain1'\"}", 403); // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), 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) - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), new File("application-reference.json"), 200); @@ -569,41 +599,41 @@ public class ApplicationApiTest extends ControllerContainerTest { HttpEntity entity = createApplicationDeployData(applicationPackage, Optional.empty()); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(entity) - .domain(athenzUserDomain).user("mytenant"), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'mytenant' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'domain1'.\"}", + .userIdentity(USER_ID), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'user.myuser' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'user'.\"}", 403); // Deleting an application for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // (Deleting it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), "", 200); // Updating a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // Change Athens domain - addTenantAthenzDomain("domain2", "mytenant"); + createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain2\", \"property\":\"property1\"}") - .domain("domain1").user(authorizedUser), + .userIdentity(authorizedUser), "{\"tenant\":\"tenant1\",\"type\":\"ATHENS\",\"athensDomain\":\"domain2\",\"property\":\"property1\",\"applications\":[]}", 200); // Deleting a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) - .domain("domain1").user(unauthorizedUser), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"User othertenant does not have write access to tenant tenant1\"}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); } @@ -613,19 +643,20 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerTester tester = controllerTester.containerTester(); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .upgradePolicy("default") - .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("invalid.domain"), AthenzService.from("service")) + .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("invalid.domain"), com.yahoo.config.provision.AthenzService.from("service")) .environment(Environment.prod) .region("us-west-1") .build(); long screwdriverProjectId = 123; - AthenzDomain domain = addTenantAthenzDomain(athenzUserDomain, "mytenant"); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); - Application application = controllerTester.createApplication(athenzUserDomain, "tenant1", "application1"); - controllerTester.authorize(domain, new ScrewdriverId(Long.toString(screwdriverProjectId)), ApplicationAction.deploy, application); + Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.id(), "tenant1", "application1"); + ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); + controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("sd" + screwdriverProjectId), + .screwdriverIdentity(screwdriverId), "{\"error-code\":\"FORBIDDEN\",\"message\":\"Athenz domain in deployment.xml: [invalid.domain] must match tenant domain: [domain1]\"}", 403); @@ -637,21 +668,23 @@ public class ApplicationApiTest extends ControllerContainerTest { ContainerTester tester = controllerTester.containerTester(); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .upgradePolicy("default") - .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), AthenzService.from("service")) + .athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service")) .environment(Environment.prod) .region("us-west-1") .build(); long screwdriverProjectId = 123; - AthenzDomain domain = addTenantAthenzDomain(athenzUserDomain, "mytenant"); + ScrewdriverId screwdriverId = new ScrewdriverId(Long.toString(screwdriverProjectId)); - Application application = controllerTester.createApplication(athenzUserDomain, "tenant1", "application1"); - controllerTester.authorize(domain, new ScrewdriverId(Long.toString(screwdriverProjectId)), ApplicationAction.deploy, application); + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + + Application application = controllerTester.createApplication(ATHENZ_TENANT_DOMAIN.id(), "tenant1", "application1"); + controllerTester.authorize(ATHENZ_TENANT_DOMAIN, screwdriverId, ApplicationAction.deploy, application); // Allow systemtest to succeed by notifying completion of system test controllerTester.notifyJobCompletion(application.id(), screwdriverProjectId, true, DeploymentJobs.JobType.component); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) .data(createApplicationDeployData(applicationPackage, Optional.of(screwdriverProjectId))) - .domain(athenzScrewdriverDomain).user("sd" + screwdriverProjectId), + .screwdriverIdentity(screwdriverId), new File("deploy-result.json")); } @@ -692,8 +725,7 @@ public class ApplicationApiTest extends ControllerContainerTest { private final String path; private final Request.Method method; private byte[] data = new byte[0]; - private String domain = "domain1"; - private String user = "mytenant"; + private AthenzIdentity identity; private String contentType = "application/json"; private String recursive; @@ -713,8 +745,8 @@ public class ApplicationApiTest extends ControllerContainerTest { } return data(out.toByteArray()).contentType(data.getContentType().getValue()); } - private RequestBuilder domain(String domain) { this.domain = domain; return this; } - private RequestBuilder user(String user) { this.user = user; return this; } + private RequestBuilder userIdentity(UserId userId) { this.identity = AthenzUser.fromUserId(userId); return this; } + private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = AthenzService.fromScrewdriverId(screwdriverId); return this; } private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; } private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; } @@ -722,10 +754,13 @@ public class ApplicationApiTest extends ControllerContainerTest { public Request get() { Request request = new Request("http://localhost:8080" + path + // user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters - "?domain=" + domain + (user == null ? "" : "&user=" + user) + - (recursive == null ? "" : "&recursive=" + recursive), + (recursive == null ? "" : "?recursive=" + recursive), data, method); request.getHeaders().put("Content-Type", contentType); + if (identity != null) { + request.getHeaders().put("Athenz-Identity-Domain", identity.getDomain().id()); + request.getHeaders().put("Athenz-Identity-Name", identity.getName()); + } return request; } } @@ -739,26 +774,27 @@ public class ApplicationApiTest extends ControllerContainerTest { * In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the * mock setup to replicate the action. */ - private AthenzDomain addTenantAthenzDomain(String domainName, String userName) { + private void createAthenzDomainWithAdmin(AthenzDomain domain, UserId userId) { AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() .getComponent(AthenzClientFactoryMock.class.getName()); - AthenzDomain athensDomain = new AthenzDomain(domainName); - AthenzDbMock.Domain domain = new AthenzDbMock.Domain(athensDomain); - domain.markAsVespaTenant(); - domain.admin(AthenzUtils.createPrincipal(new UserId(userName))); - mock.getSetup().addDomain(domain); - return athensDomain; + AthenzDbMock.Domain domainMock = new AthenzDbMock.Domain(domain); + domainMock.markAsVespaTenant(); + domainMock.admin(AthenzUser.fromUserId(userId)); + mock.getSetup().addDomain(domainMock); } /** * In production this happens outside hosted Vespa, so there is no API for it and we need to reach down into the * mock setup to replicate the action. */ - private void addScrewdriverUserToDomain(String screwdriverUserId, String domainName) { + private void addScrewdriverUserToDeployRole(ScrewdriverId screwdriverId, + AthenzDomain domain, + com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId applicationId) { AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() .getComponent(AthenzClientFactoryMock.class.getName()); - AthenzDbMock.Domain domain = mock.getSetup().domains.get(new AthenzDomain(domainName)); - domain.admin(new AthenzPrincipal(new AthenzDomain(athenzScrewdriverDomain), new UserId(screwdriverUserId))); + AthenzIdentity screwdriverIdentity = AthenzService.fromScrewdriverId(screwdriverId); + AthenzDbMock.Application athenzApplication = mock.getSetup().domains.get(domain).applications.get(applicationId); + athenzApplication.addRoleMember(ApplicationAction.deploy, screwdriverIdentity); } private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application, long projectId, @@ -773,7 +809,7 @@ public class ApplicationApiTest extends ControllerContainerTest { application.tenant().value(), application.application().value()); tester.assertResponse(request(testPath, POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request(testPath, DELETE), "Deactivated " + testPath.replaceFirst("/application/v4/", "")); @@ -784,7 +820,7 @@ public class ApplicationApiTest extends ControllerContainerTest { application.tenant().value(), application.application().value()); tester.assertResponse(request(stagingPath, POST) .data(deployData) - .domain(athenzScrewdriverDomain).user("screwdriveruser1"), + .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request(stagingPath, DELETE), "Deactivated " + stagingPath.replaceFirst("/application/v4/", "")); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java index e5898b7a593..5899c767beb 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java @@ -5,10 +5,10 @@ import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TestIdentities; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; +import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; import com.yahoo.vespa.hosted.controller.athenz.NToken; import javax.ws.rs.core.SecurityContext; @@ -20,6 +20,7 @@ import java.util.Optional; * This is necessary because filters are not currently executed when executing requests with Application. * * @author bratseth + * @author bjorncs */ @SuppressWarnings("unused") // injected public class MockAuthorizer extends Authorizer { @@ -30,10 +31,13 @@ public class MockAuthorizer extends Authorizer { /** Returns a principal given by the request parameters 'domain' and 'user' */ @Override - public Optional<Principal> getPrincipalIfAny(HttpRequest request) { - if (request.getProperty("user") == null) return Optional.empty(); - return Optional.of(new AthenzPrincipal(new AthenzDomain(request.getProperty("domain")), - new UserId(request.getProperty("user")))); + public Optional<AthenzPrincipal> getPrincipalIfAny(HttpRequest request) { + String domain = request.getHeader("Athenz-Identity-Domain"); + String name = request.getHeader("Athenz-Identity-Name"); + if (domain == null || name == null) return Optional.empty(); + return Optional.of( + new AthenzPrincipal( + AthenzUtils.createAthenzIdentity(new AthenzDomain(domain), name))); } /** Returns the hardcoded NToken of {@link TestIdentities#userId} */ @@ -42,12 +46,6 @@ public class MockAuthorizer extends Authorizer { return Optional.of(TestIdentities.userNToken); } - private static class MockPrincipal implements Principal { - - @Override - public String getName() { return TestIdentities.userId.id(); } - - } @Override protected Optional<SecurityContext> securityContextOf(HttpRequest request) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json index d3927cbcfcf..79b9a785801 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json @@ -1,5 +1,5 @@ { - "user": "mytenant", + "user": "myuser", "tenants": @include(tenant-list.json), "tenantExists": false }
\ No newline at end of file |