diff options
author | Valerij Fredriksen <freva@users.noreply.github.com> | 2021-03-19 09:37:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-19 09:37:51 +0100 |
commit | eb345d955d47577f300170cbf5d4850f9b75893d (patch) | |
tree | b586904d2e78ab71d7e7c864ebc31dedb8a4cecf | |
parent | 2dbcab338e1289d45eca0007f43c4324f028b25f (diff) | |
parent | 2be302bcbc88ca995b001cec4b13cd9bb6416a83 (diff) |
Merge pull request #17045 from vespa-engine/freva/archive-uri-api
Set & expose archive access role in /application/v4
9 files changed, 54 insertions, 3 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index 72210ec26ed..d03df9523bd 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -67,6 +67,9 @@ enum PathGroup { PathPrefix.api, "/application/v4/tenant/{tenant}/key/"), + tenantArchiveAccess(Matcher.tenant, + PathPrefix.api, + "/application/v4/tenant/{tenant}/archive-access"), billingToken(Matcher.tenant, PathPrefix.api, diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java index ad739d16ff8..b48e786c178 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java @@ -72,6 +72,11 @@ enum Policy { .on(PathGroup.tenant, PathGroup.tenantInfo, PathGroup.tenantUsers, PathGroup.applicationUsers) .in(SystemName.all())), + /** Access to set and unset archive access role under a tenant. */ + tenantArchiveAccessManagement(Privilege.grant(Action.update, Action.delete) + .on(PathGroup.tenantArchiveAccess) + .in(SystemName.all())), + /** Access to create application under a certain tenant. */ applicationCreate(Privilege.grant(Action.create) .on(PathGroup.application) diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java index aeb5419b682..a0ee0fe3548 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java @@ -63,6 +63,7 @@ public enum RoleDefinition { administrator(Policy.tenantUpdate, Policy.tenantManager, Policy.tenantDelete, + Policy.tenantArchiveAccessManagement, Policy.applicationManager, Policy.keyRevokal, Policy.paymentInstrumentRead, 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 e5ada9365f0..9bd3c3bb54c 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 @@ -1985,6 +1985,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler { toSlime(tenantQuota, usedQuota, object.setObject("quota")); + cloudTenant.archiveAccessRole().ifPresent(role -> object.setString("archiveAccessRole", role)); + break; } default: throw new IllegalArgumentException("Unexpected tenant type '" + tenant.type() + "'."); 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 b5126b7973b..4f9702669dd 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 @@ -12,6 +12,7 @@ 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. @@ -20,6 +21,8 @@ import java.util.Optional; */ 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; @@ -36,6 +39,9 @@ public class CloudTenant extends Tenant { 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(String.format("Invalid archive access role '%s': Must match expected pattern: '%s'", + archiveAccessRole.get(), VALID_ARCHIVE_ACCESS_ROLE_PATTERN.pattern())); } /** Creates a tenant with the given name, provided it passes validation. */ 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 9790c8244c8..e123c4cca62 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 @@ -120,7 +120,7 @@ public class TenantSerializerTest { new TenantSecretStore("ss1", "123", "role1"), new TenantSecretStore("ss2", "124", "role2") ), - Optional.of("role3") + Optional.of("arn:aws:iam::123456789012:role/my-role") ); 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/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java index 2bf6eb39089..10f143a8e96 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java @@ -142,12 +142,16 @@ public class ContainerTester { expectedStatusCode); } - public void assertResponse(Supplier<Request> requestSupplier, Consumer<Response> responseAssertion, int expectedStatusCode) { + public void assertResponse(Supplier<Request> requestSupplier, ConsumerThrowingException<Response> responseAssertion, int expectedStatusCode) { var request = requestSupplier.get(); FilterResult filterResult = invokeSecurityFilters(request); request = filterResult.request; Response response = filterResult.response != null ? filterResult.response : container.handleRequest(request); - responseAssertion.accept(response); + try { + responseAssertion.accept(response); + } catch (Exception e) { + throw new RuntimeException(e); + } assertEquals("Status code", expectedStatusCode, response.getStatus()); } @@ -203,5 +207,9 @@ public class ContainerTester { } } + @FunctionalInterface + public interface ConsumerThrowingException<T> { + void accept(T t) throws Exception; + } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java index b935f8cbbe4..23573f86cc3 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java @@ -88,6 +88,7 @@ public class ControllerContainerCloudTest extends ControllerContainerTest { public RequestBuilder principal(String principal) { this.principal = new SimplePrincipal(principal); return this; } public RequestBuilder user(User user) { this.user = user; return this; } public RequestBuilder roles(Set<Role> roles) { this.roles = roles; return this; } + public RequestBuilder roles(Role... roles) { return roles(Set.of(roles)); } @Override public Request get() { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java index 51e63d5b4cf..4fb91639daa 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java @@ -34,6 +34,8 @@ import static com.yahoo.application.container.handler.Request.Method.POST; import static com.yahoo.application.container.handler.Request.Method.PUT; import static com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiTest.createApplicationSubmissionData; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -195,6 +197,29 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { assertEquals(0, tenant.tenantSecretStores().size()); } + @Test + public void archive_uri_test() { + tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)), + (response) -> assertFalse(response.getBodyAsString().contains("archiveAccessRole")), + 200); + tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", PUT) + .data("{\"role\":\"dummy\"}").roles(Role.administrator(tenantName)), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Invalid archive access role 'dummy': Must match expected pattern: 'arn:aws:iam::\\\\d{12}:.+'\"}", 400); + + tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", PUT) + .data("{\"role\":\"arn:aws:iam::123456789012:role/my-role\"}").roles(Role.administrator(tenantName)), + "{\"message\":\"Archive access role set to 'arn:aws:iam::123456789012:role/my-role' for tenant scoober.\"}", 200); + tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)), + (response) -> assertTrue(response.getBodyAsString().contains("\"archiveAccessRole\":\"arn:aws:iam::123456789012:role/my-role\"")), + 200); + + tester.assertResponse(request("/application/v4/tenant/scoober/archive-access", DELETE).roles(Role.administrator(tenantName)), + "{\"message\":\"Archive access role removed for tenant scoober.\"}", 200); + tester.assertResponse(request("/application/v4/tenant/scoober", GET).roles(Role.reader(tenantName)), + (response) -> assertFalse(response.getBodyAsString().contains("archiveAccessRole")), + 200); + } + private ApplicationPackageBuilder prodBuilder() { return new ApplicationPackageBuilder() .instances("default") |