summaryrefslogtreecommitdiffstats
path: root/controller-api/src/main/java/com
diff options
context:
space:
mode:
authorOla Aunrønning <olaa@verizonmedia.com>2022-02-14 12:43:35 +0100
committerGitHub <noreply@github.com>2022-02-14 12:43:35 +0100
commit2cc844d5050017b389492274e0b373610bd061ea (patch)
treee6f1c3e81c6df5b35cd8f1789b99d2bd62bd10c6 /controller-api/src/main/java/com
parent267b6f417f4f3cfb49d82206f7ae15c4e78f8419 (diff)
parentc419f94e2a0a6b2198a629b29a11b8f667ebd8a3 (diff)
Merge pull request #21126 from vespa-engine/olaa/ssh-access-requests
Request, list, and approve ssh access
Diffstat (limited to 'controller-api/src/main/java/com')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AccessControlService.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/AthenzAccessControlService.java63
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/MockAccessControlService.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java11
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java4
7 files changed, 103 insertions, 9 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 a981b11887e..c1d70bf297d 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
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.athenz.api.AthenzUser;
import java.time.Instant;
@@ -14,5 +15,8 @@ import java.util.Collection;
*/
public interface AccessControlService {
boolean approveDataPlaneAccess(AthenzUser user, Instant expiry);
+ boolean approveSshAccess(TenantName tenantName, Instant expiry);
+ boolean requestSshAccess(TenantName tenantName);
+ boolean hasPendingAccessRequests(TenantName tenantName);
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 c2d4d4a5996..f157f88967a 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
@@ -2,15 +2,16 @@
package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzGroup;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzRole;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.client.zms.ZmsClient;
import java.time.Instant;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -19,13 +20,16 @@ public class AthenzAccessControlService implements AccessControlService {
private static final String ALLOWED_OPERATOR_GROUPNAME = "vespa-team";
private static final String DATAPLANE_ACCESS_ROLENAME = "operator-data-plane";
+ private final String TENANT_DOMAIN_PREFIX = "vespa.tenant";
private final ZmsClient zmsClient;
private final AthenzRole dataPlaneAccessRole;
private final AthenzGroup vespaTeam;
+ private final ZmsClient vespaZmsClient; //TODO: Merge ZMS clients
- public AthenzAccessControlService(ZmsClient zmsClient, AthenzDomain domain) {
+ public AthenzAccessControlService(ZmsClient zmsClient, AthenzDomain domain, ZmsClient vespaZmsClient) {
this.zmsClient = zmsClient;
+ this.vespaZmsClient = vespaZmsClient;
this.dataPlaneAccessRole = new AthenzRole(domain, DATAPLANE_ACCESS_ROLENAME);
this.vespaTeam = new AthenzGroup(domain, ALLOWED_OPERATOR_GROUPNAME);
}
@@ -36,7 +40,7 @@ public class AthenzAccessControlService implements AccessControlService {
if(!isVespaTeamMember(user)) {
throw new IllegalArgumentException(String.format("User %s requires manual approval, please contact Vespa team", user.getName()));
}
- Map<AthenzUser, String> users = zmsClient.listPendingRoleApprovals(dataPlaneAccessRole);
+ Map<AthenzIdentity, String> users = zmsClient.listPendingRoleApprovals(dataPlaneAccessRole);
if (users.containsKey(user)) {
zmsClient.approvePendingRoleMembership(dataPlaneAccessRole, user, expiry, Optional.empty());
return true;
@@ -53,6 +57,59 @@ public class AthenzAccessControlService implements AccessControlService {
.collect(Collectors.toList());
}
+ /**
+ * @return Whether the ssh access role has any pending role membership requests
+ */
+ @Override
+ public boolean hasPendingAccessRequests(TenantName tenantName) {
+ var role = sshRole(tenantName);
+ var pendingApprovals = vespaZmsClient.listPendingRoleApprovals(role);
+ return pendingApprovals.containsKey(vespaTeam);
+ }
+
+ /**
+ * @return true if access has been granted - false if already member
+ */
+ @Override
+ public boolean approveSshAccess(TenantName tenantName, Instant expiry) {
+ var role = sshRole(tenantName);
+ if (vespaZmsClient.getMembership(role, vespaTeam))
+ return false;
+
+ if (!hasPendingAccessRequests(tenantName)) {
+ vespaZmsClient.addRoleMember(role, vespaTeam, Optional.empty());
+ }
+ // TODO: Pass along auth0 credentials
+ vespaZmsClient.approvePendingRoleMembership(role, vespaTeam, expiry, Optional.empty());
+ return true;
+ }
+
+ /**
+ * @return true if access has been requested - false if already member
+ */
+ @Override
+ public boolean requestSshAccess(TenantName tenantName) {
+ var role = sshRole(tenantName);
+ if (vespaZmsClient.getMembership(role, vespaTeam))
+ return false;
+ vespaZmsClient.addRoleMember(role, vespaTeam, Optional.empty());
+ return true;
+ }
+
+ private AthenzRole sshRole(TenantName tenantName) {
+ return new AthenzRole(getOrCreateTenantDomain(tenantName), "ssh_access");
+ }
+
+ private AthenzDomain getOrCreateTenantDomain(TenantName tenantName) {
+ var domain = new AthenzDomain(TENANT_DOMAIN_PREFIX + "." + tenantName.value());
+
+ if (vespaZmsClient.getDomainList(domain.getName()).isEmpty()) {
+ vespaZmsClient.createSubdomain(new AthenzDomain(TENANT_DOMAIN_PREFIX), tenantName.value());
+ }
+
+ return domain;
+ }
+
public boolean isVespaTeamMember(AthenzUser user) {
return zmsClient.getGroupMembership(vespaTeam, user);
}
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 a0cc0d1ae1c..f906172dba0 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
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.controller.api.integration.athenz;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.athenz.api.AthenzUser;
import java.time.Instant;
@@ -28,6 +29,21 @@ public class MockAccessControlService implements AccessControlService {
return Set.copyOf(members);
}
+ @Override
+ public boolean approveSshAccess(TenantName tenantName, Instant expiry) {
+ return false;
+ }
+
+ @Override
+ public boolean requestSshAccess(TenantName tenantName) {
+ return false;
+ }
+
+ @Override
+ public boolean hasPendingAccessRequests(TenantName tenantName) {
+ return false;
+ }
+
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 1b00368b73e..63212c9c200 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
@@ -200,12 +200,12 @@ public class ZmsClientMock implements ZmsClient {
}
@Override
- public Map<AthenzUser,String> listPendingRoleApprovals(AthenzRole athenzRole) {
+ public Map<AthenzIdentity,String> listPendingRoleApprovals(AthenzRole athenzRole) {
return Map.of();
}
@Override
- public void approvePendingRoleMembership(AthenzRole athenzRole, AthenzUser athenzUser, Instant expiry, Optional<String> reason) {
+ public void approvePendingRoleMembership(AthenzRole athenzRole, AthenzIdentity athenzIdentity, Instant expiry, Optional<String> reason) {
}
@Override
@@ -255,6 +255,10 @@ public class ZmsClientMock implements ZmsClient {
public void deleteRole(AthenzRole athenzRole) {
athenz.domains.get(athenzRole.domain()).roles.removeIf(role -> role.name().equals(athenzRole.roleName()));
}
+
+ @Override
+ public void createSubdomain(AthenzDomain parent, String name) {}
+
@Override
public void close() {}
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 8bd7a9e726d..3a5bb96d985 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
@@ -233,7 +233,13 @@ enum PathGroup {
secretStore(Matcher.tenant, "/application/v4/tenant/{tenant}/secret-store/{*}"),
/** Paths used to proxy Horizon metric requests */
- horizonProxy("/horizon/v1/{*}");
+ horizonProxy("/horizon/v1/{*}"),
+
+ /** Paths used to list and request access to tenant resources */
+ accessRequests(Matcher.tenant, "/application/v4/tenant/{tenant}/access/request/{*}"),
+
+ /** Paths used to approve requests to access tenant resources */
+ accessRequestApproval(Matcher.tenant, "/application/v4/tenant/{tenant}/access/approve/{*}");
final List<String> pathSpecs;
final List<Matcher> matchers;
@@ -281,9 +287,10 @@ enum PathGroup {
return EnumSet.complementOf(EnumSet.copyOf(pathGroups));
}
- static Set<PathGroup> billingPaths() {
+ static Set<PathGroup> operatorRestrictedPaths() {
var paths = billingPathsNoToken();
paths.add(PathGroup.billingToken);
+ paths.add(accessRequestApproval);
return paths;
}
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 d40027d91a7..4d9a16c34f0 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
@@ -22,7 +22,7 @@ enum Policy {
/** Full access to everything. */
operator(Privilege.grant(Action.all())
- .on(PathGroup.allExcept(PathGroup.billingPaths()))
+ .on(PathGroup.allExcept(PathGroup.operatorRestrictedPaths()))
.in(SystemName.all()),
Privilege.grant(Action.read)
.on(PathGroup.billingPathsNoToken())
@@ -188,6 +188,10 @@ enum Policy {
.on(PathGroup.billingList, PathGroup.billing)
.in(SystemName.PublicCd, SystemName.Public)),
+ accessRequests(Privilege.grant(Action.all())
+ .on(PathGroup.accessRequests, PathGroup.accessRequestApproval)
+ .in(SystemName.PublicCd)),
+
/** Invoice management */
hostedAccountant(Privilege.grant(Action.all())
.on(PathGroup.hostedAccountant, PathGroup.accountant)
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 aed5c08f0db..7f6e77faf03 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
@@ -75,7 +75,9 @@ public enum RoleDefinition {
Policy.paymentInstrumentDelete,
Policy.paymentInstrumentCreate,
Policy.planUpdate,
- Policy.billingInformationRead),
+ Policy.billingInformationRead,
+ Policy.accessRequests
+ ),
/** Headless — the application specific role identified by deployment keys for production */
headless(Policy.submission),