Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v17] Update AccessList Proto, AccessList & AccessListMember types, add Hierarchy utils #48184

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
570 changes: 349 additions & 221 deletions api/gen/proto/go/teleport/accesslist/v1/accesslist.pb.go

Large diffs are not rendered by default.

27 changes: 26 additions & 1 deletion api/proto/teleport/accesslist/v1/accesslist.proto
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ message AccessListOwner {
// ineligible_status describes if this owner is eligible or not
// and if not, describes how they're lacking eligibility.
IneligibleStatus ineligible_status = 3;

// membership_kind describes the type of membership, either
// `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`.
MembershipKind membership_kind = 4;
}

// AccessListAudit describes the audit configuration for an Access List.
Expand Down Expand Up @@ -197,6 +201,21 @@ message MemberSpec {
// ineligible_status describes if this member is eligible or not
// and if not, describes how they're lacking eligibility.
IneligibleStatus ineligible_status = 7;

// membership_kind describes the type of membership, either
// `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`.
MembershipKind membership_kind = 9;
}

// MembershipKind represents the different kinds of list membership
enum MembershipKind {
// MEMBERSHIP_KIND_UNSPECIFIED represents list members that are of
// unknown membership kind, defaulting to being treated as type USER
MEMBERSHIP_KIND_UNSPECIFIED = 0;
// MEMBERSHIP_KIND_USER represents list members that are normal users
MEMBERSHIP_KIND_USER = 1;
// MEMBERSHIP_KIND_LIST represents list members that are nested Access Lists
MEMBERSHIP_KIND_LIST = 2;
}

// IneligibleStatus describes how the user is ineligible.
Expand Down Expand Up @@ -268,6 +287,12 @@ message ReviewChanges {

// AccessListStatus contains dynamic fields calculated during retrieval.
message AccessListStatus {
// member_count is the number of members in the in the Access List.
// member_count is the number of members in the Access List.
optional uint32 member_count = 1;
// member_list_count is the number of nested list members in the Access List.
optional uint32 member_list_count = 2;
// owner_of describes Access Lists where this Access List is an explicit owner.
repeated string owner_of = 3;
// member_of describes Access Lists where this Access List is an explicit member.
repeated string member_of = 4;
}
44 changes: 37 additions & 7 deletions api/types/accesslist/accesslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"

accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/header"
"github.com/gravitational/teleport/api/types/header/convert/legacy"
Expand Down Expand Up @@ -75,6 +76,20 @@ func parseReviewFrequency(input string) ReviewFrequency {
return 0
}

// MaxAllowedDepth is the maximum allowed depth for nested access lists.
const MaxAllowedDepth = 10

var (
// MembershipKindUnspecified is the default membership kind (treated as 'user').
MembershipKindUnspecified = accesslistv1.MembershipKind_MEMBERSHIP_KIND_UNSPECIFIED.String()

// MembershipKindUser is the user membership kind.
MembershipKindUser = accesslistv1.MembershipKind_MEMBERSHIP_KIND_USER.String()

// MembershipKindList is the list membership kind.
MembershipKindList = accesslistv1.MembershipKind_MEMBERSHIP_KIND_LIST.String()
)

// ReviewDayOfMonth is the day of month the review should be repeated on.
type ReviewDayOfMonth int

Expand Down Expand Up @@ -123,7 +138,7 @@ type AccessList struct {
Spec Spec `json:"spec" yaml:"spec"`

// Status contains dynamically calculated fields.
Status Status `json:"-" yaml:"-"`
Status Status `json:"status" yaml:"status"`
}

// Spec is the specification for an access list.
Expand Down Expand Up @@ -167,6 +182,10 @@ type Owner struct {

// IneligibleStatus describes the reason why this owner is not eligible.
IneligibleStatus string `json:"ineligible_status" yaml:"ineligible_status"`

// MembershipKind describes the kind of ownership,
// either "MEMBERSHIP_KIND_USER" or "MEMBERSHIP_KIND_LIST".
MembershipKind string `json:"membership_kind" yaml:"membership_kind"`
}

// Audit describes the audit configuration for an access list.
Expand Down Expand Up @@ -224,7 +243,14 @@ type Grants struct {
// Status contains dynamic fields calculated during retrieval.
type Status struct {
// MemberCount is the number of members in the access list.
MemberCount *uint32
MemberCount *uint32 `json:"-" yaml:"-"`
// MemberListCount is the number of members in the access list that are lists themselves.
MemberListCount *uint32 `json:"-" yaml:"-"`

// OwnerOf is a list of Access List UUIDs where this access list is an explicit owner.
OwnerOf []string `json:"owner_of" yaml:"owner_of"`
// MemberOf is a list of Access List UUIDs where this access list is an explicit member.
MemberOf []string `json:"member_of" yaml:"member_of"`
}

// NewAccessList will create a new access list.
Expand Down Expand Up @@ -286,10 +312,6 @@ func (a *AccessList) CheckAndSetDefaults() error {
a.Spec.Audit.Notifications.Start = twoWeeks
}

if len(a.Spec.Grants.Roles) == 0 && len(a.Spec.Grants.Traits) == 0 {
return trace.BadParameter("grants must specify at least one role or trait")
}

// Deduplicate owners. The backend will currently prevent this, but it's possible that access lists
// were created with duplicated owners before the backend checked for duplicate owners. In order to
// ensure that these access lists are backwards compatible, we'll deduplicate them here.
Expand All @@ -299,6 +321,9 @@ func (a *AccessList) CheckAndSetDefaults() error {
if owner.Name == "" {
return trace.BadParameter("owner name is missing")
}
if owner.MembershipKind == "" {
owner.MembershipKind = MembershipKindUser
}

if _, ok := ownerMap[owner.Name]; ok {
continue
Expand All @@ -317,7 +342,7 @@ func (a *AccessList) GetOwners() []Owner {
return a.Spec.Owners
}

// GetOwners returns the list of owners from the access list.
// SetOwners sets the owners of the access list.
func (a *AccessList) SetOwners(owners []Owner) {
a.Spec.Owners = owners
}
Expand All @@ -337,6 +362,11 @@ func (a *AccessList) GetGrants() Grants {
return a.Spec.Grants
}

// GetOwnerGrants returns the owner grants from the access list.
func (a *AccessList) GetOwnerGrants() Grants {
return a.Spec.OwnerGrants
}

// GetMetadata returns metadata. This is specifically for conforming to the Resource interface,
// and should be removed when possible.
func (a *AccessList) GetMetadata() types.Metadata {
Expand Down
98 changes: 80 additions & 18 deletions api/types/accesslist/convert/v1/accesslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,10 @@ func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accessl

owners := make([]accesslist.Owner, len(msg.Spec.Owners))
for i, owner := range msg.Spec.Owners {
owners[i] = accesslist.Owner{
Name: owner.Name,
Description: owner.Description,
// Set it to empty as default.
// Must provide as options to set it with the provided value.
IneligibleStatus: "",
}
owners[i] = FromOwnerProto(owner)
// Set IneligibleStatus to empty as default.
// Must provide as options to set it with the provided value.
owners[i].IneligibleStatus = ""
}

var ownerGrants accesslist.Grants
Expand All @@ -94,10 +91,24 @@ func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accessl
}

var memberCount *uint32
var memberListCount *uint32
if msg.Status != nil && msg.Status.MemberCount != nil {
memberCount = new(uint32)
*memberCount = *msg.Status.MemberCount
}
if msg.Status != nil && msg.Status.MemberListCount != nil {
memberListCount = new(uint32)
*memberListCount = *msg.Status.MemberListCount
}

var ownerOf []string
var memberOf []string
if msg.Status != nil && msg.Status.OwnerOf != nil {
ownerOf = msg.Status.OwnerOf
}
if msg.Status != nil && msg.Status.MemberOf != nil {
memberOf = msg.Status.MemberOf
}

accessList, err := accesslist.NewAccessList(headerv1.FromMetadataProto(msg.Header.Metadata), accesslist.Spec{
Title: msg.Spec.Title,
Expand Down Expand Up @@ -126,7 +137,10 @@ func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accessl
return nil, trace.Wrap(err)
}
accessList.Status = accesslist.Status{
MemberCount: memberCount,
MemberCount: memberCount,
MemberListCount: memberListCount,
OwnerOf: ownerOf,
MemberOf: memberOf,
}

for _, opt := range opts {
Expand All @@ -140,15 +154,7 @@ func FromProto(msg *accesslistv1.AccessList, opts ...AccessListOption) (*accessl
func ToProto(accessList *accesslist.AccessList) *accesslistv1.AccessList {
owners := make([]*accesslistv1.AccessListOwner, len(accessList.Spec.Owners))
for i, owner := range accessList.Spec.Owners {
var ineligibleStatus accesslistv1.IneligibleStatus
if enumVal, ok := accesslistv1.IneligibleStatus_value[owner.IneligibleStatus]; ok {
ineligibleStatus = accesslistv1.IneligibleStatus(enumVal)
}
owners[i] = &accesslistv1.AccessListOwner{
Name: owner.Name,
Description: owner.Description,
IneligibleStatus: ineligibleStatus,
}
owners[i] = ToOwnerProto(owner)
}

var ownerGrants *accesslistv1.AccessListGrants
Expand All @@ -173,10 +179,24 @@ func ToProto(accessList *accesslist.AccessList) *accesslistv1.AccessList {
}

var memberCount *uint32
var memberListCount *uint32
if accessList.Status.MemberCount != nil {
memberCount = new(uint32)
*memberCount = *accessList.Status.MemberCount
}
if accessList.Status.MemberListCount != nil {
memberListCount = new(uint32)
*memberListCount = *accessList.Status.MemberListCount
}

var ownerOf []string
var memberOf []string
if accessList.Status.OwnerOf != nil {
ownerOf = accessList.Status.OwnerOf
}
if accessList.Status.MemberOf != nil {
memberOf = accessList.Status.MemberOf
}

return &accesslistv1.AccessList{
Header: headerv1.ToResourceHeaderProto(accessList.ResourceHeader),
Expand Down Expand Up @@ -209,11 +229,53 @@ func ToProto(accessList *accesslist.AccessList) *accesslistv1.AccessList {
OwnerGrants: ownerGrants,
},
Status: &accesslistv1.AccessListStatus{
MemberCount: memberCount,
MemberCount: memberCount,
MemberListCount: memberListCount,
OwnerOf: ownerOf,
MemberOf: memberOf,
},
}
}

// ToOwnerProto converts an internal access list owner into a v1 access list owner object.
func ToOwnerProto(owner accesslist.Owner) *accesslistv1.AccessListOwner {
var ineligibleStatus accesslistv1.IneligibleStatus
if owner.IneligibleStatus != "" {
if enumVal, ok := accesslistv1.IneligibleStatus_value[owner.IneligibleStatus]; ok {
ineligibleStatus = accesslistv1.IneligibleStatus(enumVal)
}
} else {
ineligibleStatus = accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_UNSPECIFIED
}

var kind accesslistv1.MembershipKind
if enumVal, ok := accesslistv1.MembershipKind_value[owner.MembershipKind]; ok {
kind = accesslistv1.MembershipKind(enumVal)
}

return &accesslistv1.AccessListOwner{
Name: owner.Name,
Description: owner.Description,
IneligibleStatus: ineligibleStatus,
MembershipKind: kind,
}
}

// FromOwnerProto converts a v1 access list owner into an internal access list owner object.
func FromOwnerProto(protoOwner *accesslistv1.AccessListOwner) accesslist.Owner {
ineligibleStatus := ""
if protoOwner.IneligibleStatus != accesslistv1.IneligibleStatus_INELIGIBLE_STATUS_UNSPECIFIED {
ineligibleStatus = protoOwner.IneligibleStatus.String()
}

return accesslist.Owner{
Name: protoOwner.Name,
Description: protoOwner.Description,
IneligibleStatus: ineligibleStatus,
MembershipKind: protoOwner.MembershipKind.String(),
}
}

// WithOwnersIneligibleStatusField sets the "ineligibleStatus" field to the provided proto value.
func WithOwnersIneligibleStatusField(protoOwners []*accesslistv1.AccessListOwner) AccessListOption {
return func(a *accesslist.AccessList) {
Expand Down
8 changes: 8 additions & 0 deletions api/types/accesslist/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ type AccessListMemberSpec struct {

// IneligibleStatus describes the reason why this member is not eligible.
IneligibleStatus string `json:"ineligible_status" yaml:"ineligible_status"`

// MembershipKind describes the kind of membership,
// either "MEMBERSHIP_KIND_USER" or "MEMBERSHIP_KIND_LIST".
MembershipKind string `json:"membership_kind" yaml:"membership_kind"`
}

// NewAccessListMember will create a new access listm member.
Expand All @@ -86,6 +90,10 @@ func (a *AccessListMember) CheckAndSetDefaults() error {
return trace.Wrap(err)
}

if a.Spec.MembershipKind == "" {
a.Spec.MembershipKind = MembershipKindUser
}

if a.Spec.AccessList == "" {
return trace.BadParameter("access list is missing")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ resource, which you can apply after installing the Teleport Kubernetes operator.
|---|---|---|
|description|string|description is the plaintext description of the owner and why they are an owner.|
|ineligible_status|string or integer|ineligible_status describes if this owner is eligible or not and if not, describes how they're lacking eligibility. Can be either the string or the integer representation of each option.|
|membership_kind|string or integer|membership_kind describes the type of membership, either `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`. Can be either the string or the integer representation of each option.|
|name|string|name is the username of the owner.|

### spec.ownership_requires
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Optional:
Optional:

- `description` (String) description is the plaintext description of the owner and why they are an owner.
- `membership_kind` (Number) membership_kind describes the type of membership, either `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`.
- `name` (String) name is the username of the owner.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ Optional:
Optional:

- `description` (String) description is the plaintext description of the owner and why they are an owner.
- `membership_kind` (Number) membership_kind describes the type of membership, either `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`.
- `name` (String) name is the username of the owner.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ spec:
description: ineligible_status describes if this owner is eligible
or not and if not, describes how they're lacking eligibility.
x-kubernetes-int-or-string: true
membership_kind:
description: membership_kind describes the type of membership,
either `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`.
x-kubernetes-int-or-string: true
name:
description: name is the username of the owner.
type: string
Expand Down
Loading
Loading