diff options
8 files changed, 89 insertions, 2 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AccessControlService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AccessControlService.java index a08319055ff..b270c27092f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AccessControlService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AccessControlService.java @@ -19,5 +19,7 @@ public interface AccessControlService { boolean approveSshAccess(TenantName tenantName, Instant expiry, OAuthCredentials oAuthCredentials); boolean requestSshAccess(TenantName tenantName); boolean hasPendingAccessRequests(TenantName tenantName); + boolean hasPreapprovedAccess(TenantName tenantName); + void setPreapprovedAccess(TenantName tenantName, boolean preapproved); Collection<AthenzUser> listMembers(); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzAccessControlService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzAccessControlService.java index a3f789149cf..6b91f49af8e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzAccessControlService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzAccessControlService.java @@ -110,6 +110,25 @@ public class AthenzAccessControlService implements AccessControlService { return true; } + public boolean hasPreapprovedAccess(TenantName tenantName) { + var role = sshRole(tenantName); + + if (!vespaZmsClient.listRoles(role.domain()).contains(role)) + return true; // true by default + + return !vespaZmsClient.isSelfServeRole(role); + } + + public void setPreapprovedAccess(TenantName tenantName, boolean preapprovedAccess) { + var role = sshRole(tenantName); + + var attributes = Map.<String, Object>of( + "selfServe", !preapprovedAccess, + "reviewEnabled", !preapprovedAccess + ); + vespaZmsClient.createRole(role, attributes); + } + private AthenzRole sshRole(TenantName tenantName) { return new AthenzRole(getOrCreateTenantDomain(tenantName), "ssh_access"); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/MockAccessControlService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/MockAccessControlService.java index b8106450705..505ee97bdf5 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/MockAccessControlService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/MockAccessControlService.java @@ -45,6 +45,16 @@ public class MockAccessControlService implements AccessControlService { return false; } + @Override + public boolean hasPreapprovedAccess(TenantName tenantName) { + return false; + } + + @Override + public void setPreapprovedAccess(TenantName tenantName, boolean preapproved) { + + } + public void addPendingMember(AthenzUser user) { pendingMembers.add(user); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java index 38b2a36a348..62a999bb7a6 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java @@ -256,6 +256,11 @@ public class ZmsClientMock implements ZmsClient { public void createSubdomain(AthenzDomain parent, String name) {} @Override + public boolean isSelfServeRole(AthenzRole role) { + return false; + } + + @Override public void close() {} private static AthenzDomain getTenantDomain(AthenzResourceName resource) { 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 673581b3626..3bcf62fde21 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 @@ -294,6 +294,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { if (path.matches("/application/v4/tenant/{tenant}")) return updateTenant(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/access/request/operator")) return requestSshAccess(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/access/approve/operator")) return approveAccessRequest(path.get("tenant"), request); + if (path.matches("/application/v4/tenant/{tenant}/access/preapprove/operator")) return addPreapprovedAccess(path.get("tenant")); 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); @@ -341,6 +342,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { 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}/access/preapprove/operator")) return removePreapprovedAccess(path.get("tenant")); 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); @@ -414,9 +416,12 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { if (controller.tenants().require(TenantName.from(tenantName)).type() != Tenant.Type.cloud) return ErrorResponse.badRequest("Can only see access requests for cloud tenants"); - var pendingRequests = controller.serviceRegistry().accessControlService().hasPendingAccessRequests(TenantName.from(tenantName)); + var accessControlService = controller.serviceRegistry().accessControlService(); + var pendingRequests = accessControlService.hasPendingAccessRequests(TenantName.from(tenantName)); + var preapprovedAccess = accessControlService.hasPreapprovedAccess(TenantName.from(tenantName)); var slime = new Slime(); slime.setObject().setBool("hasPendingRequests", pendingRequests); + slime.setObject().setBool("preapprovedAccess", preapprovedAccess); return new SlimeJsonResponse(slime); } @@ -448,6 +453,24 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { return new MessageResponse("OK"); } + private HttpResponse addPreapprovedAccess(String tenantName) { + return setPreapprovedAccess(tenantName, true); + } + + private HttpResponse removePreapprovedAccess(String tenantName) { + return setPreapprovedAccess(tenantName, false); + } + + private HttpResponse setPreapprovedAccess(String tenantName, boolean preapprovedAccess) { + var tenant = TenantName.from(tenantName); + + if (controller.tenants().require(tenant).type() != Tenant.Type.cloud) + return ErrorResponse.badRequest("Can only set access privel for cloud tenants"); + + controller.serviceRegistry().accessControlService().setPreapprovedAccess(tenant, preapprovedAccess); + return new MessageResponse("OK"); + } + private HttpResponse tenantInfo(String tenantName, HttpRequest request) { return controller.tenants().get(TenantName.from(tenantName)) .filter(tenant -> tenant.type() == Tenant.Type.cloud) diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java index 32f54255262..23c530402b9 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java @@ -406,6 +406,17 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient { execute(request, response -> readEntity(response, Void.class)); } + public boolean isSelfServeRole(AthenzRole role) { + URI uri = zmsUrl.resolve(String.format("domain/%s/role/%s", role.domain().getName(), role.roleName())); + var request = RequestBuilder.get(uri).build(); + var roleEntity = execute(request, response -> readEntity(response, RoleEntity.class)); + + if (roleEntity.selfServe() == null || roleEntity.reviewEnabled() == null) + return false; + + return roleEntity.selfServe() && roleEntity.reviewEnabled(); + } + private static Header createCookieHeader(OAuthCredentials oAuthCredentials) { return new BasicHeader("Cookie", oAuthCredentials.asCookie()); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java index 95b7d9b8976..611fe7aa451 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java @@ -80,5 +80,7 @@ public interface ZmsClient extends AutoCloseable { void createSubdomain(AthenzDomain parent, String name); + boolean isSelfServeRole(AthenzRole role); + void close(); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/RoleEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/RoleEntity.java index 28b1f5d3206..f0a498ed644 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/RoleEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/RoleEntity.java @@ -15,11 +15,18 @@ import java.util.List; public class RoleEntity { private final String roleName; private final List<Member> roleMembers; + private final Boolean selfServe; + private final Boolean reviewEnabled; @JsonCreator - public RoleEntity(@JsonProperty("roleName") String roleName, @JsonProperty("roleMembers") List<Member> roleMembers) { + public RoleEntity(@JsonProperty("roleName") String roleName, + @JsonProperty("roleMembers") List<Member> roleMembers, + @JsonProperty("selfServe") Boolean selfServe, + @JsonProperty("reviewEnabled") Boolean reviewEnabled) { this.roleName = roleName; this.roleMembers = roleMembers; + this.selfServe = selfServe; + this.reviewEnabled = reviewEnabled; } public String roleName() { @@ -30,6 +37,14 @@ public class RoleEntity { return roleMembers; } + public Boolean selfServe() { + return selfServe; + } + + public Boolean reviewEnabled() { + return reviewEnabled; + } + @JsonIgnoreProperties(ignoreUnknown = true) public static final class Member { private final String memberName; |