summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMorten Tokle <morten.tokle@gmail.com>2017-12-05 09:40:43 +0100
committerGitHub <noreply@github.com>2017-12-05 09:40:43 +0100
commit2e399df23fd841161e5913ae154800b2db39eaae (patch)
tree6f0ce3772ab98ce4c806b47e161a765db7976693 /controller-server
parent69c0128865dac863aba8d1d8ea511eec850e2ade (diff)
parent15df8a6d11c0883045ab6bdb2330e537728faf4f (diff)
Merge pull request #4342 from vespa-engine/bjorncs/enforce-athenz-principal
Bjorncs/enforce athenz principal
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzIdentity.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzPrincipal.java38
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzService.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUser.java56
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/AthenzUtils.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/NToken.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZmsClient.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/ZtsClient.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZmsClientImpl.java51
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/ZtsClientImpl.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/AthenzDbMock.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZmsClientMock.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java56
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/TestIdentities.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/NTokenValidatorTest.java21
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerControllerTester.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java230
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json2
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