aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java89
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java29
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java28
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java31
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java54
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java5
9 files changed, 208 insertions, 39 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java
new file mode 100644
index 00000000000..6336fddb8be
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/ArchiveAccess.java
@@ -0,0 +1,89 @@
+package com.yahoo.vespa.hosted.controller.tenant;
+
+import com.yahoo.text.Text;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class ArchiveAccess {
+
+ private static final Pattern VALID_AWS_ARCHIVE_ACCESS_ROLE_PATTERN = Pattern.compile("arn:aws:iam::\\d{12}:.+");
+ private static final Pattern VALID_GCP_ARCHIVE_ACCESS_MEMBER_PATTERN = Pattern.compile("(?<prefix>[a-zA-Z]+):.+");
+
+ private static final Set<String> gcpMemberPrefixes = Set.of("user", "serviceAccount", "group", "domain");
+
+ // AWS IAM Role
+ private final Optional<String> awsRole;
+ // GCP Member
+ private final Optional<String> gcpMember;
+
+ public ArchiveAccess() {
+ this(Optional.empty(), Optional.empty());
+ }
+
+ public ArchiveAccess(Optional<String> awsRole, Optional<String> gcpMember) {
+ this.awsRole = awsRole;
+ this.gcpMember = gcpMember;
+
+ awsRole.ifPresent(role -> validateAWSIAMRole(role));
+ gcpMember.ifPresent(member -> validateGCPMember(member));
+ }
+
+ public static ArchiveAccess fromAWSRole(String role) {
+ return new ArchiveAccess(Optional.of(role), Optional.empty());
+ }
+
+ public static ArchiveAccess fromGCPMember(String member) {
+ return new ArchiveAccess(Optional.empty(), Optional.of(member));
+ }
+
+ private void validateAWSIAMRole(String role) {
+ if (!VALID_AWS_ARCHIVE_ACCESS_ROLE_PATTERN.matcher(role).matches()) {
+ throw new IllegalArgumentException(Text.format("Invalid archive access role '%s': Must match expected pattern: '%s'",
+ awsRole.get(), VALID_AWS_ARCHIVE_ACCESS_ROLE_PATTERN.pattern()));
+ }
+ if (role.length() > 100) {
+ throw new IllegalArgumentException("Invalid archive access role too long, must be 100 or less characters");
+ }
+ }
+
+ private void validateGCPMember(String member) {
+ var matcher = VALID_GCP_ARCHIVE_ACCESS_MEMBER_PATTERN.matcher(member);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(Text.format("Invalid GCP archive access member '%s': Must match expected pattern: '%s'",
+ gcpMember.get(), VALID_GCP_ARCHIVE_ACCESS_MEMBER_PATTERN.pattern()));
+ }
+ var prefix = matcher.group("prefix");
+ if (!gcpMemberPrefixes.contains(prefix)) {
+ throw new IllegalArgumentException(Text.format("Invalid GCP member prefix '%s', must be one of '%s'",
+ prefix, gcpMemberPrefixes.stream().collect(Collectors.joining(", "))));
+ }
+ if (!"domain".equals(prefix) && !member.contains("@")) {
+ throw new IllegalArgumentException(Text.format("Invalid GCP member '%s', prefix '%s' must be followed by an email id", member, prefix));
+ }
+ }
+
+ public Optional<String> awsRole() {
+ return awsRole;
+ }
+
+ public Optional<String> gcpMember() {
+ return gcpMember;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ArchiveAccess that = (ArchiveAccess) o;
+ return awsRole.equals(that.awsRole) && gcpMember.equals(that.gcpMember);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(awsRole, gcpMember);
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
index 9a4e04ebb3a..953468a28a7 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.tenant;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.yahoo.config.provision.TenantName;
-import com.yahoo.text.Text;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import java.security.Principal;
@@ -13,7 +12,6 @@ import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.regex.Pattern;
/**
* A paying tenant in a Vespa cloud service.
@@ -22,29 +20,22 @@ import java.util.regex.Pattern;
*/
public class CloudTenant extends Tenant {
- private static final Pattern VALID_ARCHIVE_ACCESS_ROLE_PATTERN = Pattern.compile("arn:aws:iam::\\d{12}:.+");
-
private final Optional<Principal> creator;
private final BiMap<PublicKey, Principal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
- private final Optional<String> archiveAccessRole;
+ private final ArchiveAccess archiveAccess;
/** Public for the serialization layer — do not use! */
public CloudTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
- List<TenantSecretStore> tenantSecretStores, Optional<String> archiveAccessRole) {
+ List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess) {
super(name, createdAt, lastLoginInfo, Optional.empty());
this.creator = creator;
this.developerKeys = developerKeys;
this.info = Objects.requireNonNull(info);
this.tenantSecretStores = tenantSecretStores;
- this.archiveAccessRole = archiveAccessRole;
- if (!archiveAccessRole.map(role -> VALID_ARCHIVE_ACCESS_ROLE_PATTERN.matcher(role).matches()).orElse(true))
- throw new IllegalArgumentException(Text.format("Invalid archive access role '%s': Must match expected pattern: '%s'",
- archiveAccessRole.get(), VALID_ARCHIVE_ACCESS_ROLE_PATTERN.pattern()));
- if (archiveAccessRole.map(role -> role.length() > 100).orElse(false))
- throw new IllegalArgumentException("Invalid archive access role too long, must be 100 or less characters");
+ this.archiveAccess = Objects.requireNonNull(archiveAccess);
}
/** Creates a tenant with the given name, provided it passes validation. */
@@ -53,7 +44,7 @@ public class CloudTenant extends Tenant {
createdAt,
LastLoginInfo.EMPTY,
Optional.ofNullable(creator),
- ImmutableBiMap.of(), TenantInfo.empty(), List.of(), Optional.empty());
+ ImmutableBiMap.of(), TenantInfo.empty(), List.of(), new ArchiveAccess());
}
/** The user that created the tenant */
@@ -68,7 +59,7 @@ public class CloudTenant extends Tenant {
/** An iam role which is allowed to access the S3 (log, dump) archive) */
public Optional<String> archiveAccessRole() {
- return archiveAccessRole;
+ return archiveAccess.awsRole();
}
/** Returns the set of developer keys and their corresponding developers for this tenant. */
@@ -79,6 +70,16 @@ public class CloudTenant extends Tenant {
return tenantSecretStores;
}
+ /**
+ * Returns archive access archive bucket access string
+ *
+ * For AWS is this the IAM role
+ * For GCP it is a Google Workspace group
+ */
+ public ArchiveAccess archiveAccess() {
+ return archiveAccess;
+ }
+
@Override
public Type type() {
return Type.cloud;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
index 7a0e60aacb4..4f58e87035b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
@@ -8,11 +8,11 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.security.KeyUtils;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.athenz.api.AthenzDomain;
-import com.yahoo.vespa.curator.Lock;
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.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -128,26 +128,26 @@ public abstract class LockedTenant {
private final BiMap<PublicKey, Principal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
- private final Optional<String> archiveAccessRole;
+ private final ArchiveAccess archiveAccess;
private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
- List<TenantSecretStore> tenantSecretStores, Optional<String> archiveAccessRole) {
+ List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess) {
super(name, createdAt, lastLoginInfo);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
this.creator = creator;
this.info = info;
this.tenantSecretStores = tenantSecretStores;
- this.archiveAccessRole = archiveAccessRole;
+ this.archiveAccess = archiveAccess;
}
private Cloud(CloudTenant tenant) {
- this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccessRole());
+ this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccess());
}
@Override
public CloudTenant get() {
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
@@ -155,38 +155,38 @@ public abstract class LockedTenant {
if (keys.containsKey(key))
throw new IllegalArgumentException("Key " + KeyUtils.toPem(key) + " is already owned by " + keys.get(key));
keys.put(key, principal);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess);
}
public Cloud withoutDeveloperKey(PublicKey key) {
BiMap<PublicKey, Principal> keys = HashBiMap.create(developerKeys);
keys.remove(key);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess);
}
public Cloud withInfo(TenantInfo newInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccess);
}
@Override
public LockedTenant with(LastLoginInfo lastLoginInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
public Cloud withSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.add(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess);
}
public Cloud withoutSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.remove(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccessRole);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess);
}
- public Cloud withArchiveAccessRole(Optional<String> role) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, role);
+ public Cloud withArchiveAccess(ArchiveAccess archiveAccess) {
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index 00e38abcba7..f00e179cfcf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInf
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -77,6 +78,10 @@ public class TenantSerializer {
private static final String lastLoginInfoField = "lastLoginInfo";
private static final String secretStoresField = "secretStores";
private static final String archiveAccessRoleField = "archiveAccessRole";
+ private static final String archiveAccessField = "archiveAccess";
+ private static final String awsArchiveAccessRoleField = "awsArchiveAccessRole";
+ private static final String gcpArchiveAccessMemberField = "gcpArchiveAccessMember";
+
private static final String awsIdField = "awsId";
private static final String roleField = "role";
@@ -117,7 +122,13 @@ public class TenantSerializer {
toSlime(legacyBillingInfo, root.setObject(billingInfoField));
toSlime(tenant.info(), root);
toSlime(tenant.tenantSecretStores(), root);
- tenant.archiveAccessRole().ifPresent(role -> root.setString(archiveAccessRoleField, role));
+ toSlime(tenant.archiveAccess(), root);
+ }
+
+ private void toSlime(ArchiveAccess archiveAccess, Cursor root) {
+ Cursor object = root.setObject(archiveAccessField);
+ archiveAccess.awsRole().ifPresent(role -> object.setString(awsArchiveAccessRoleField, role));
+ archiveAccess.gcpMember().ifPresent(member -> object.setString(gcpArchiveAccessMemberField, member));
}
private void toSlime(DeletedTenant tenant, Cursor root) {
@@ -175,8 +186,8 @@ public class TenantSerializer {
BiMap<PublicKey, Principal> developerKeys = developerKeysFromSlime(tenantObject.field(pemDeveloperKeysField));
TenantInfo info = tenantInfoFromSlime(tenantObject.field(tenantInfoField));
List<TenantSecretStore> tenantSecretStores = secretStoresFromSlime(tenantObject.field(secretStoresField));
- Optional<String> archiveAccessRole = SlimeUtils.optionalString(tenantObject.field(archiveAccessRoleField));
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
+ ArchiveAccess archiveAccess = archiveAccessFromSlime(tenantObject);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess);
}
private DeletedTenant deletedTenantFrom(Inspector tenantObject) {
@@ -195,6 +206,20 @@ public class TenantSerializer {
return keys.build();
}
+ ArchiveAccess archiveAccessFromSlime(Inspector tenantObject) {
+ // TODO(enygaard, 2022-05-24): Remove when all tenants have been rewritten to use ArchiveAccess object
+ Optional<String> archiveAccessRole = SlimeUtils.optionalString(tenantObject.field(archiveAccessRoleField));
+ if (archiveAccessRole.isPresent()) {
+ return new ArchiveAccess(archiveAccessRole, Optional.empty());
+ }
+ Inspector object = tenantObject.field(archiveAccessField);
+ if (!object.valid()) {
+ return new ArchiveAccess();
+ }
+ Optional<String> awsArchiveAccessRole = SlimeUtils.optionalString(object.field(awsArchiveAccessRoleField));
+ Optional<String> gcpArchiveAccessMember = SlimeUtils.optionalString(object.field(gcpArchiveAccessMemberField));
+ return new ArchiveAccess(awsArchiveAccessRole, gcpArchiveAccessMember);
+ }
TenantInfo tenantInfoFromSlime(Inspector infoObject) {
if (!infoObject.valid()) return TenantInfo.empty();
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 91b76ac8d05..0c564a51f37 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
@@ -108,6 +108,7 @@ import com.yahoo.vespa.hosted.controller.routing.rotation.RotationStatus;
import com.yahoo.vespa.hosted.controller.security.AccessControlRequests;
import com.yahoo.vespa.hosted.controller.security.Credentials;
import com.yahoo.vespa.hosted.controller.support.access.SupportAccess;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -1041,7 +1042,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccessRole(Optional.of(role));
+ lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess(Optional.of(role), Optional.empty()));
controller.tenants().store(lockedTenant);
});
@@ -1053,7 +1054,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccessRole(Optional.empty());
+ lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess());
controller.tenants().store(lockedTenant);
});
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
index b97743f4d44..12418656c2f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java
@@ -9,6 +9,7 @@ import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveBucket;
import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import org.junit.Test;
@@ -59,7 +60,7 @@ public class ArchiveAccessMaintainerTest {
private TenantName createTenantWithAccessRole(ControllerTester tester, String tenantName, String role) {
var tenant = tester.createTenant(tenantName, Tenant.Type.cloud);
tester.controller().tenants().lockOrThrow(tenant, LockedTenant.Cloud.class, lockedTenant -> {
- lockedTenant = lockedTenant.withArchiveAccessRole(Optional.of(role));
+ lockedTenant = lockedTenant.withArchiveAccess(new ArchiveAccess(Optional.of(role), Optional.empty()));
tester.controller().tenants().store(lockedTenant);
});
return tenant;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
index 75dbebe96ff..5666f8bafd8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java
@@ -20,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
@@ -60,7 +61,7 @@ public class NotificationsDbTest {
List.of(TenantContacts.Audience.NOTIFICATIONS),
email)))),
List.of(),
- Optional.empty());
+ new ArchiveAccess());
private static final List<Notification> notifications = List.of(
notification(1001, Type.deployment, Level.error, NotificationSource.from(tenant), "tenant msg"),
notification(1101, Type.applicationPackage, Level.warning, NotificationSource.from(TenantAndApplicationId.from(tenant.value(), "app1")), "app msg"),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
index e0d14f19f21..431cfbbcbad 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
@@ -6,12 +6,14 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.security.KeyUtils;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.athenz.api.AthenzDomain;
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.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
@@ -101,7 +103,7 @@ public class TenantSerializerTest {
otherPublicKey, new SimplePrincipal("jane")),
TenantInfo.empty(),
List.of(),
- Optional.empty()
+ new ArchiveAccess()
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
@@ -123,13 +125,61 @@ public class TenantSerializerTest {
new TenantSecretStore("ss1", "123", "role1"),
new TenantSecretStore("ss2", "124", "role2")
),
- Optional.of("arn:aws:iam::123456789012:role/my-role")
+ new ArchiveAccess(Optional.of("arn:aws:iam::123456789012:role/my-role"), Optional.empty())
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.info(), serialized.info());
assertEquals(tenant.tenantSecretStores(), serialized.tenantSecretStores());
}
+ @Test
+ public void cloud_tenant_with_old_archive_access_serialization() {
+ var json = "{\n" +
+ " \"name\": \"elderly-lady\",\n" +
+ " \"type\": \"cloud\",\n" +
+ " \"createdAt\": 1234,\n" +
+ " \"lastLoginInfo\": {\n" +
+ " \"user\": 123,\n" +
+ " \"developer\": 456\n" +
+ " },\n" +
+ " \"creator\": \"foobar-user\",\n" +
+ " \"pemDeveloperKeys\": [\n" +
+ " {\n" +
+ " \"key\": \"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuKVFA8dXk43kVfYKzkUqhEY2rDT9\\nz/4jKSTHwbYR8wdsOSrJGVEUPbS2nguIJ64OJH7gFnxM6sxUVj+Nm2HlXw==\\n-----END PUBLIC KEY-----\\n\",\n" +
+ " \"user\": \"joe\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"key\": \"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFELzPyinTfQ/sZnTmRp5E4Ve/sbE\\npDhJeqczkyFcT2PysJ5sZwm7rKPEeXDOhzTPCyRvbUqc2SGdWbKUGGa/Yw==\\n-----END PUBLIC KEY-----\\n\",\n" +
+ " \"user\": \"jane\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"billingInfo\": {\n" +
+ " \"customerId\": \"customer\",\n" +
+ " \"productCode\": \"Vespa\"\n" +
+ " },\n" +
+ " \"archiveAccessRole\": \"arn:aws:iam::123456789012:role/my-role\"\n" +
+ "}";
+ var tenant = (CloudTenant) serializer.tenantFrom(SlimeUtils.jsonToSlime(json));
+ assertEquals("arn:aws:iam::123456789012:role/my-role", tenant.archiveAccess().awsRole().get());
+ assertFalse(tenant.archiveAccess().gcpMember().isPresent());
+ }
+
+ @Test
+ public void cloud_tenant_with_archive_access() {
+ CloudTenant tenant = new CloudTenant(TenantName.from("elderly-lady"),
+ Instant.ofEpochMilli(1234L),
+ lastLoginInfo(123L, 456L, null),
+ Optional.of(new SimplePrincipal("foobar-user")),
+ ImmutableBiMap.of(publicKey, new SimplePrincipal("joe"),
+ otherPublicKey, new SimplePrincipal("jane")),
+ TenantInfo.empty(),
+ List.of(),
+ new ArchiveAccess(Optional.of("arn:aws:iam::123456789012:role/my-role"), Optional.of("user:foo@example.com"))
+ );
+ CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
+ assertEquals(serialized.archiveAccess().awsRole().get(), "arn:aws:iam::123456789012:role/my-role");
+ assertEquals(serialized.archiveAccess().gcpMember().get(), "user:foo@example.com");
+ }
@Test
public void cloud_tenant_with_tenant_info_partial() {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
index 15c7dbf73ab..9024d7c8e7e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper;
+import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
@@ -76,7 +77,7 @@ public class SignatureFilterTest {
ImmutableBiMap.of(),
TenantInfo.empty(),
List.of(),
- Optional.empty()));
+ new ArchiveAccess()));
tester.curator().writeApplication(new Application(appId, tester.clock().instant()));
}
@@ -122,7 +123,7 @@ public class SignatureFilterTest {
ImmutableBiMap.of(publicKey, () -> "user"),
TenantInfo.empty(),
List.of(),
- Optional.empty()));
+ new ArchiveAccess()));
verifySecurityContext(requestOf(signer.signed(request.copy(), Method.POST, () -> new ByteArrayInputStream(hiBytes)), hiBytes),
new SecurityContext(new SimplePrincipal("user"),
Set.of(Role.reader(id.tenant()),