diff options
6 files changed, 115 insertions, 11 deletions
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 233759f47a7..0be32165916 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 @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.athenz; import com.yahoo.vespa.athenz.api.AthenzDomain; +import com.yahoo.vespa.athenz.api.AthenzGroup; import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.athenz.client.zms.ZmsClient; @@ -14,18 +15,25 @@ import java.util.stream.Collectors; 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 ZmsClient zmsClient; private final AthenzRole dataPlaneAccessRole; + private final AthenzGroup vespaTeam; public AthenzAccessControlService(ZmsClient zmsClient, AthenzDomain domain) { this.zmsClient = zmsClient; this.dataPlaneAccessRole = new AthenzRole(domain, DATAPLANE_ACCESS_ROLENAME); + this.vespaTeam = new AthenzGroup(domain, ALLOWED_OPERATOR_GROUPNAME); } @Override public boolean approveDataPlaneAccess(AthenzUser user, Instant expiry) { + // Can only approve team members, other members must be manually approved + if(!isVespaTeamMember(user)) { + throw new IllegalArgumentException(String.format("User %s requires manual approval, please contact Vespa team", user.getName())); + } List<AthenzUser> users = zmsClient.listPendingRoleApprovals(dataPlaneAccessRole); if (users.contains(user)) { zmsClient.approvePendingRoleMembership(dataPlaneAccessRole, user, expiry); @@ -42,4 +50,8 @@ public class AthenzAccessControlService implements AccessControlService { .map(AthenzUser.class::cast) .collect(Collectors.toList()); } + + 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/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java index deeecf217e7..ed84a9b0a76 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.athenz; 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.AthenzResourceName; import com.yahoo.vespa.athenz.api.AthenzRole; @@ -98,6 +99,11 @@ public class ZmsClientMock implements ZmsClient { } @Override + public boolean getGroupMembership(AthenzGroup group, AthenzIdentity identity) { + return false; + } + + @Override public List<AthenzDomain> getDomainList(String prefix) { log("getDomainList()"); return new ArrayList<>(athenz.domains.keySet()); diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzGroup.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzGroup.java new file mode 100644 index 00000000000..2608af381a2 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AthenzGroup.java @@ -0,0 +1,41 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.athenz.api; + +import java.util.Objects; + +public class AthenzGroup { + private final AthenzDomain domain; + private final String groupName; + + public AthenzGroup(AthenzDomain domain, String groupName) { + this.domain = domain; + this.groupName = groupName; + } + + public AthenzGroup(String domain, String groupName) { + this.domain = new AthenzDomain(domain); + this.groupName = groupName; + } + + public AthenzDomain domain() { + return domain; + } + + public String groupName() { + return groupName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AthenzGroup that = (AthenzGroup) o; + return Objects.equals(domain, that.domain) && Objects.equals(groupName, that.groupName); + } + + @Override + public int hashCode() { + return Objects.hash(domain, groupName); + } +} 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 f73ac9c3535..5817eb0c8d2 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.athenz.client.zms; 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.AthenzResourceName; import com.yahoo.vespa.athenz.api.AthenzRole; @@ -112,7 +113,7 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient { @Override public void addRoleMember(AthenzRole role, AthenzIdentity member) { URI uri = zmsUrl.resolve(String.format("domain/%s/role/%s/member/%s", role.domain().getName(), role.roleName(), member.getFullName())); - MembershipEntity membership = new MembershipEntity(member.getFullName(), true, role.roleName(), null); + MembershipEntity membership = new MembershipEntity.RoleMembershipEntity(member.getFullName(), true, role.roleName(), null); HttpUriRequest request = RequestBuilder.put(uri) .setEntity(toJsonStringEntity(membership)) .build(); @@ -133,6 +134,18 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient { .setUri(uri) .build(); return execute(request, response -> { + MembershipEntity membership = readEntity(response, MembershipEntity.GroupMembershipEntity.class); + return membership.isMember; + }); + } + + @Override + public boolean getGroupMembership(AthenzGroup group, AthenzIdentity identity) { + URI uri = zmsUrl.resolve(String.format("domain/%s/group/%s/member/%s", group.domain().getName(), group.groupName(), identity.getFullName())); + HttpUriRequest request = RequestBuilder.get() + .setUri(uri) + .build(); + return execute(request, response -> { MembershipEntity membership = readEntity(response, MembershipEntity.class); return membership.isMember; }); @@ -223,7 +236,7 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient { @Override public void approvePendingRoleMembership(AthenzRole athenzRole, AthenzUser athenzUser, Instant expiry) { URI uri = zmsUrl.resolve(String.format("domain/%s/role/%s/member/%s/decision", athenzRole.domain().getName(), athenzRole.roleName(), athenzUser.getFullName())); - MembershipEntity membership = new MembershipEntity(athenzUser.getFullName(), true, athenzRole.roleName(), Long.toString(expiry.getEpochSecond())); + MembershipEntity membership = new MembershipEntity.RoleMembershipEntity(athenzUser.getFullName(), true, athenzRole.roleName(), Long.toString(expiry.getEpochSecond())); HttpUriRequest request = RequestBuilder.put() .setUri(uri) .setEntity(toJsonStringEntity(membership)) 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 15e8ba77850..245078e3679 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.athenz.client.zms; 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.AthenzResourceName; import com.yahoo.vespa.athenz.api.AthenzRole; @@ -36,6 +37,8 @@ public interface ZmsClient extends AutoCloseable { boolean getMembership(AthenzRole role, AthenzIdentity identity); + boolean getGroupMembership(AthenzGroup group, AthenzIdentity identity); + List<AthenzDomain> getDomainList(String prefix); boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity); diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipEntity.java index d0672473776..33acf0e1c90 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/MembershipEntity.java @@ -16,17 +16,14 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class MembershipEntity { public final String memberName; public final boolean isMember; - public final String roleName; public final String expiration; @JsonCreator public MembershipEntity(@JsonProperty("memberName") String memberName, @JsonProperty("isMember") boolean isMember, - @JsonProperty("roleName") String roleName, @JsonProperty("expiration") String expiration) { this.memberName = memberName; this.isMember = isMember; - this.roleName = roleName; this.expiration = expiration; } @@ -40,13 +37,45 @@ public class MembershipEntity { return isMember; } - @JsonGetter("roleName") - public String roleName() { - return roleName; - } - @JsonGetter("expiration") public String expiration() { return expiration; } -} + + public static class RoleMembershipEntity extends MembershipEntity { + public final String roleName; + + @JsonCreator + public RoleMembershipEntity(@JsonProperty("memberName") String memberName, + @JsonProperty("isMember") boolean isMember, + @JsonProperty("roleName") String roleName, + @JsonProperty("expiration") String expiration) { + super(memberName, isMember, expiration); + this.roleName = roleName; + } + + @JsonGetter("roleName") + public String roleName() { + return roleName; + } + + } + + public static class GroupMembershipEntity extends MembershipEntity { + public final String groupName; + + @JsonCreator + public GroupMembershipEntity(@JsonProperty("memberName") String memberName, + @JsonProperty("isMember") boolean isMember, + @JsonProperty("groupName") String groupName, + @JsonProperty("expiration") String expiration) { + super(memberName, isMember, expiration); + this.groupName = groupName; + } + + @JsonGetter("groupName") + public String roleName() { + return groupName; + } + } +}
\ No newline at end of file |