summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Eriksen <andreer@verizonmedia.com>2021-03-16 18:33:05 +0100
committerGitHub <noreply@github.com>2021-03-16 18:33:05 +0100
commit6699b03fcb353d0ba19910421b959bfc879294f8 (patch)
tree868e9a4e24f904d944810512506f60a0620797aa
parent6f089c406599c9092a999a1cdb0cad7521ddca07 (diff)
add archive access role to tenant (#16986)
* add archive access role to tenant * simplify
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java6
6 files changed, 72 insertions, 17 deletions
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 22fa25fc711..f25f1c64372 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
@@ -121,22 +121,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 Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info, List<TenantSecretStore> tenantSecretStores) {
+ private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
+ BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
+ List<TenantSecretStore> tenantSecretStores, Optional<String> archiveAccessRole) {
super(name, createdAt, lastLoginInfo);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
this.creator = creator;
this.info = info;
this.tenantSecretStores = tenantSecretStores;
+ this.archiveAccessRole = archiveAccessRole;
}
private Cloud(CloudTenant tenant) {
- this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), Optional.empty(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores());
+ this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), Optional.empty(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccessRole());
}
@Override
public CloudTenant get() {
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
@@ -144,34 +148,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);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccessRole);
}
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);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccessRole);
}
public Cloud withInfo(TenantInfo newInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccessRole);
}
@Override
public LockedTenant with(LastLoginInfo lastLoginInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
}
public Cloud withSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.add(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccessRole);
}
public Cloud withoutSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.remove(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccessRole);
+ }
+
+ public Cloud withArchiveAccessRole(Optional<String> role) {
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, role);
}
}
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 beaf546930f..8cb87b8a72d 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
@@ -71,6 +71,7 @@ public class TenantSerializer {
private static final String tenantInfoField = "info";
private static final String lastLoginInfoField = "lastLoginInfo";
private static final String secretStoresField = "secretStores";
+ private static final String archiveAccessRoleField = "archiveAccessRole";
private static final String awsIdField = "awsId";
private static final String roleField = "role";
@@ -110,6 +111,7 @@ public class TenantSerializer {
toSlime(legacyBillingInfo, root.setObject(billingInfoField));
toSlime(tenant.info(), root);
toSlime(tenant.tenantSecretStores(), root);
+ tenant.archiveAccessRole().ifPresent(role -> root.setString(archiveAccessRoleField, role));
}
private void developerKeysToSlime(BiMap<PublicKey, Principal> keys, Cursor array) {
@@ -162,7 +164,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));
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores);
+ Optional<String> archiveAccessRole = SlimeUtils.optionalString(tenantObject.field(archiveAccessRoleField));
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccessRole);
}
private BiMap<PublicKey, Principal> developerKeysFromSlime(Inspector array) {
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 96fcb1d7f56..9aa5c17270b 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
@@ -271,6 +271,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse handlePUT(Path path, HttpRequest request) {
if (path.matches("/application/v4/tenant/{tenant}")) return updateTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/info")) return updateTenantInfo(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return allowArchiveAccess(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}")) return addSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
@@ -315,6 +316,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse handleDELETE(Path path, HttpRequest request) {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/key")) return removeDeveloperKey(path.get("tenant"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/archive-access")) return removeArchiveAccess(path.get("tenant"));
if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}")) return deleteSecretStore(path.get("tenant"), path.get("name"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deployment")) return removeAllProdDeployments(path.get("tenant"), path.get("application"));
@@ -733,6 +735,37 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
+ private HttpResponse allowArchiveAccess(String tenantName, HttpRequest request) {
+ if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
+ throw new IllegalArgumentException("Tenant '" + tenantName + "' is not a cloud tenant");
+
+ var data = toSlime(request.getData()).get();
+ var role = mandatory("role", data).asString();
+
+ if (role.isBlank()) {
+ return ErrorResponse.badRequest("Archive access role can't be whitespace only");
+ }
+
+ controller.tenants().lockOrThrow(TenantName.from(tenantName), LockedTenant.Cloud.class, lockedTenant -> {
+ lockedTenant = lockedTenant.withArchiveAccessRole(Optional.of(role));
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("Archive access role set to '" + role + "' for tenant " + tenantName + ".");
+ }
+
+ private HttpResponse removeArchiveAccess(String tenantName) {
+ if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud)
+ 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());
+ controller.tenants().store(lockedTenant);
+ });
+
+ return new MessageResponse("Archive access role removed for tenant " + tenantName + ".");
+ }
+
private HttpResponse patchApplication(String tenantName, String applicationName, HttpRequest request) {
Inspector requestObject = toSlime(request.getData()).get();
StringJoiner messageBuilder = new StringJoiner("\n").setEmptyValue("No applicable changes.");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
index 888bfd7fe42..b5126b7973b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -24,16 +24,18 @@ public class CloudTenant extends Tenant {
private final BiMap<PublicKey, Principal> developerKeys;
private final TenantInfo info;
private final List<TenantSecretStore> tenantSecretStores;
-
+ private final Optional<String> archiveAccessRole;
/** 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) {
+ BiMap<PublicKey, Principal> developerKeys, TenantInfo info,
+ List<TenantSecretStore> tenantSecretStores, Optional<String> archiveAccessRole) {
super(name, createdAt, lastLoginInfo, Optional.empty());
this.creator = creator;
this.developerKeys = developerKeys;
this.info = Objects.requireNonNull(info);
this.tenantSecretStores = tenantSecretStores;
+ this.archiveAccessRole = archiveAccessRole;
}
/** Creates a tenant with the given name, provided it passes validation. */
@@ -42,7 +44,7 @@ public class CloudTenant extends Tenant {
createdAt,
LastLoginInfo.EMPTY,
Optional.ofNullable(creator),
- ImmutableBiMap.of(), TenantInfo.EMPTY, List.of());
+ ImmutableBiMap.of(), TenantInfo.EMPTY, List.of(), Optional.empty());
}
/** The user that created the tenant */
@@ -55,6 +57,11 @@ public class CloudTenant extends Tenant {
return info;
}
+ /** An iam role which is allowed to access the S3 (log, dump) archive) */
+ public Optional<String> archiveAccessRole() {
+ return archiveAccessRole;
+ }
+
/** Returns the set of developer keys and their corresponding developers for this tenant. */
public BiMap<PublicKey, Principal> developerKeys() { return developerKeys; }
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 1467c1f6392..9790c8244c8 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
@@ -97,7 +97,8 @@ public class TenantSerializerTest {
ImmutableBiMap.of(publicKey, new SimplePrincipal("joe"),
otherPublicKey, new SimplePrincipal("jane")),
TenantInfo.EMPTY,
- List.of()
+ List.of(),
+ Optional.empty()
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
@@ -118,7 +119,8 @@ public class TenantSerializerTest {
List.of(
new TenantSecretStore("ss1", "123", "role1"),
new TenantSecretStore("ss2", "124", "role2")
- )
+ ),
+ Optional.of("role3")
);
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.info(), serialized.info());
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 85aa01a11be..efca19a61e1 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
@@ -75,7 +75,8 @@ public class SignatureFilterTest {
Optional.empty(),
ImmutableBiMap.of(),
TenantInfo.EMPTY,
- List.of()));
+ List.of(),
+ Optional.empty()));
tester.curator().writeApplication(new Application(appId, tester.clock().instant()));
}
@@ -120,7 +121,8 @@ public class SignatureFilterTest {
Optional.empty(),
ImmutableBiMap.of(publicKey, () -> "user"),
TenantInfo.EMPTY,
- List.of()));
+ List.of(),
+ Optional.empty()));
verifySecurityContext(requestOf(signer.signed(request.copy(), Method.POST, () -> new ByteArrayInputStream(hiBytes)), hiBytes),
new SecurityContext(new SimplePrincipal("user"),
Set.of(Role.reader(id.tenant()),