mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
feat: Allow including Access Lists as nested members and owners (#38738)
- Recursively check for accesslist membership - Allow adding/removing/listing included access lists in acl commands - Add a recursive test - Use dynamic access lists structure from RFD - Resolve proto changes - Exclude 'list' members from Access List memberCount - Calc Access List member count with members of type 'list' excluded, return seperately to front end - Update examples/integrations - Update crd docs - Update tf docs - Perform calculation of inherited roles/traits to AccessList service in order to utilize cache and minimize number of requests. - Grant Okta integration RO for Access Lists - Update AccessListMember-* events - Include count for inherited grants - Include MembershipKind of affected member(s) - Emit inherited grants / members' MembershipKind for AccessListMember-* events - Update notified owners for Access Requests - Ensure dynamic owners are notified for Access Requests - Ensure dynamic owners are notified via Slack integration - Optionally pass an AbortSignal to `fetchAccessLists` in Web UI - Replace usages of `services.IsAccessListOwner/IsAccessListMember` with equivelant funcs from `Hierarchy` - Remove final references to AccessListMembershipChecker - Don't allow ACL deletion when member/owner in other lists - Guard Access List deletion behind membership/ownership checks for List - Expose Hierarchy func to recursively get all members - Tidy UserLoginStateGenerator logic involving ACL Membership/Ownership Co-authored-by: Maxim Dietz <[email protected]>
This commit is contained in:
co-authored by
Maxim Dietz
parent
2ad2d990f4
commit
fa97b8f90d
@@ -23,6 +23,7 @@ import (
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
conv "github.com/gravitational/teleport/api/types/accesslist/convert/v1"
|
||||
traitv1 "github.com/gravitational/teleport/api/types/trait/convert/v1"
|
||||
)
|
||||
|
||||
// Client is an access list client that conforms to the following lib/services interfaces:
|
||||
@@ -113,6 +114,21 @@ func (c *Client) GetAccessListsToReview(ctx context.Context) ([]*accesslist.Acce
|
||||
return accessLists, nil
|
||||
}
|
||||
|
||||
// GetInheritedGrants returns grants inherited by access list accessListID from parent access lists.
|
||||
func (c *Client) GetInheritedGrants(ctx context.Context, accessListID string) (*accesslist.Grants, error) {
|
||||
resp, err := c.grpcClient.GetInheritedGrants(ctx, &accesslistv1.GetInheritedGrantsRequest{
|
||||
AccessListId: accessListID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return &accesslist.Grants{
|
||||
Roles: resp.Grants.Roles,
|
||||
Traits: traitv1.FromProto(resp.Grants.Traits),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpsertAccessList creates or updates an access list resource.
|
||||
func (c *Client) UpsertAccessList(ctx context.Context, accessList *accesslist.AccessList) (*accesslist.AccessList, error) {
|
||||
resp, err := c.grpcClient.UpsertAccessList(ctx, &accesslistv1.UpsertAccessListRequest{
|
||||
@@ -151,15 +167,15 @@ func (c *Client) DeleteAllAccessLists(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// CountAccessListMembers will count all access list members.
|
||||
func (c *Client) CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error) {
|
||||
func (c *Client) CountAccessListMembers(ctx context.Context, accessListName string) (users uint32, lists uint32, err error) {
|
||||
resp, err := c.grpcClient.CountAccessListMembers(ctx, &accesslistv1.CountAccessListMembersRequest{
|
||||
AccessListName: accessListName,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, trace.Wrap(err)
|
||||
return 0, 0, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return resp.Count, nil
|
||||
return resp.Count, resp.ListCount, nil
|
||||
}
|
||||
|
||||
// ListAccessListMembers returns a paginated list of all access list members for an access list.
|
||||
@@ -221,6 +237,26 @@ func (c *Client) GetAccessListMember(ctx context.Context, accessList string, mem
|
||||
return member, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// GetAccessListOwners returns a list of all owners in an Access List, including those inherited from nested Access Lists.
|
||||
//
|
||||
// Returned Owners are not validated for ownership requirements – use `IsAccessListOwner` for validation.
|
||||
func (c *Client) GetAccessListOwners(ctx context.Context, accessListName string) ([]*accesslist.Owner, error) {
|
||||
resp, err := c.grpcClient.GetAccessListOwners(ctx, &accesslistv1.GetAccessListOwnersRequest{
|
||||
AccessList: accessListName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
owners := make([]*accesslist.Owner, 0, len(resp.Owners))
|
||||
for _, owner := range resp.Owners {
|
||||
ownerProto := conv.FromOwnerProto(owner)
|
||||
owners = append(owners, &ownerProto)
|
||||
}
|
||||
|
||||
return owners, nil
|
||||
}
|
||||
|
||||
// UpsertAccessListMember creates or updates an access list member resource.
|
||||
func (c *Client) UpsertAccessListMember(ctx context.Context, member *accesslist.AccessListMember) (*accesslist.AccessListMember, error) {
|
||||
resp, err := c.grpcClient.UpsertAccessListMember(ctx, &accesslistv1.UpsertAccessListMemberRequest{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@ const (
|
||||
AccessListService_ListAccessListMembers_FullMethodName = "/teleport.accesslist.v1.AccessListService/ListAccessListMembers"
|
||||
AccessListService_ListAllAccessListMembers_FullMethodName = "/teleport.accesslist.v1.AccessListService/ListAllAccessListMembers"
|
||||
AccessListService_GetAccessListMember_FullMethodName = "/teleport.accesslist.v1.AccessListService/GetAccessListMember"
|
||||
AccessListService_GetAccessListOwners_FullMethodName = "/teleport.accesslist.v1.AccessListService/GetAccessListOwners"
|
||||
AccessListService_UpsertAccessListMember_FullMethodName = "/teleport.accesslist.v1.AccessListService/UpsertAccessListMember"
|
||||
AccessListService_UpdateAccessListMember_FullMethodName = "/teleport.accesslist.v1.AccessListService/UpdateAccessListMember"
|
||||
AccessListService_DeleteAccessListMember_FullMethodName = "/teleport.accesslist.v1.AccessListService/DeleteAccessListMember"
|
||||
@@ -58,6 +59,7 @@ const (
|
||||
AccessListService_DeleteAccessListReview_FullMethodName = "/teleport.accesslist.v1.AccessListService/DeleteAccessListReview"
|
||||
AccessListService_AccessRequestPromote_FullMethodName = "/teleport.accesslist.v1.AccessListService/AccessRequestPromote"
|
||||
AccessListService_GetSuggestedAccessLists_FullMethodName = "/teleport.accesslist.v1.AccessListService/GetSuggestedAccessLists"
|
||||
AccessListService_GetInheritedGrants_FullMethodName = "/teleport.accesslist.v1.AccessListService/GetInheritedGrants"
|
||||
)
|
||||
|
||||
// AccessListServiceClient is the client API for AccessListService service.
|
||||
@@ -93,6 +95,9 @@ type AccessListServiceClient interface {
|
||||
ListAllAccessListMembers(ctx context.Context, in *ListAllAccessListMembersRequest, opts ...grpc.CallOption) (*ListAllAccessListMembersResponse, error)
|
||||
// GetAccessListMember returns the specified access list member resource.
|
||||
GetAccessListMember(ctx context.Context, in *GetAccessListMemberRequest, opts ...grpc.CallOption) (*Member, error)
|
||||
// GetAccessListOwners returns a list of all owners in an Access List,
|
||||
// including those inherited from nested Access Lists.
|
||||
GetAccessListOwners(ctx context.Context, in *GetAccessListOwnersRequest, opts ...grpc.CallOption) (*GetAccessListOwnersResponse, error)
|
||||
// UpsertAccessListMember creates or updates an access list member resource.
|
||||
UpsertAccessListMember(ctx context.Context, in *UpsertAccessListMemberRequest, opts ...grpc.CallOption) (*Member, error)
|
||||
// UpdateAccessListMember conditionally updates an access list member resource.
|
||||
@@ -125,6 +130,8 @@ type AccessListServiceClient interface {
|
||||
// GetSuggestedAccessLists returns suggested access lists for an access
|
||||
// request.
|
||||
GetSuggestedAccessLists(ctx context.Context, in *GetSuggestedAccessListsRequest, opts ...grpc.CallOption) (*GetSuggestedAccessListsResponse, error)
|
||||
// GetInheritedGrants returns the inherited grants for an access list.
|
||||
GetInheritedGrants(ctx context.Context, in *GetInheritedGrantsRequest, opts ...grpc.CallOption) (*GetInheritedGrantsResponse, error)
|
||||
}
|
||||
|
||||
type accessListServiceClient struct {
|
||||
@@ -255,6 +262,16 @@ func (c *accessListServiceClient) GetAccessListMember(ctx context.Context, in *G
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *accessListServiceClient) GetAccessListOwners(ctx context.Context, in *GetAccessListOwnersRequest, opts ...grpc.CallOption) (*GetAccessListOwnersResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetAccessListOwnersResponse)
|
||||
err := c.cc.Invoke(ctx, AccessListService_GetAccessListOwners_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *accessListServiceClient) UpsertAccessListMember(ctx context.Context, in *UpsertAccessListMemberRequest, opts ...grpc.CallOption) (*Member, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(Member)
|
||||
@@ -375,6 +392,16 @@ func (c *accessListServiceClient) GetSuggestedAccessLists(ctx context.Context, i
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *accessListServiceClient) GetInheritedGrants(ctx context.Context, in *GetInheritedGrantsRequest, opts ...grpc.CallOption) (*GetInheritedGrantsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetInheritedGrantsResponse)
|
||||
err := c.cc.Invoke(ctx, AccessListService_GetInheritedGrants_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AccessListServiceServer is the server API for AccessListService service.
|
||||
// All implementations must embed UnimplementedAccessListServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -408,6 +435,9 @@ type AccessListServiceServer interface {
|
||||
ListAllAccessListMembers(context.Context, *ListAllAccessListMembersRequest) (*ListAllAccessListMembersResponse, error)
|
||||
// GetAccessListMember returns the specified access list member resource.
|
||||
GetAccessListMember(context.Context, *GetAccessListMemberRequest) (*Member, error)
|
||||
// GetAccessListOwners returns a list of all owners in an Access List,
|
||||
// including those inherited from nested Access Lists.
|
||||
GetAccessListOwners(context.Context, *GetAccessListOwnersRequest) (*GetAccessListOwnersResponse, error)
|
||||
// UpsertAccessListMember creates or updates an access list member resource.
|
||||
UpsertAccessListMember(context.Context, *UpsertAccessListMemberRequest) (*Member, error)
|
||||
// UpdateAccessListMember conditionally updates an access list member resource.
|
||||
@@ -440,6 +470,8 @@ type AccessListServiceServer interface {
|
||||
// GetSuggestedAccessLists returns suggested access lists for an access
|
||||
// request.
|
||||
GetSuggestedAccessLists(context.Context, *GetSuggestedAccessListsRequest) (*GetSuggestedAccessListsResponse, error)
|
||||
// GetInheritedGrants returns the inherited grants for an access list.
|
||||
GetInheritedGrants(context.Context, *GetInheritedGrantsRequest) (*GetInheritedGrantsResponse, error)
|
||||
mustEmbedUnimplementedAccessListServiceServer()
|
||||
}
|
||||
|
||||
@@ -486,6 +518,9 @@ func (UnimplementedAccessListServiceServer) ListAllAccessListMembers(context.Con
|
||||
func (UnimplementedAccessListServiceServer) GetAccessListMember(context.Context, *GetAccessListMemberRequest) (*Member, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccessListMember not implemented")
|
||||
}
|
||||
func (UnimplementedAccessListServiceServer) GetAccessListOwners(context.Context, *GetAccessListOwnersRequest) (*GetAccessListOwnersResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccessListOwners not implemented")
|
||||
}
|
||||
func (UnimplementedAccessListServiceServer) UpsertAccessListMember(context.Context, *UpsertAccessListMemberRequest) (*Member, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpsertAccessListMember not implemented")
|
||||
}
|
||||
@@ -522,6 +557,9 @@ func (UnimplementedAccessListServiceServer) AccessRequestPromote(context.Context
|
||||
func (UnimplementedAccessListServiceServer) GetSuggestedAccessLists(context.Context, *GetSuggestedAccessListsRequest) (*GetSuggestedAccessListsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetSuggestedAccessLists not implemented")
|
||||
}
|
||||
func (UnimplementedAccessListServiceServer) GetInheritedGrants(context.Context, *GetInheritedGrantsRequest) (*GetInheritedGrantsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetInheritedGrants not implemented")
|
||||
}
|
||||
func (UnimplementedAccessListServiceServer) mustEmbedUnimplementedAccessListServiceServer() {}
|
||||
func (UnimplementedAccessListServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -759,6 +797,24 @@ func _AccessListService_GetAccessListMember_Handler(srv interface{}, ctx context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AccessListService_GetAccessListOwners_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetAccessListOwnersRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AccessListServiceServer).GetAccessListOwners(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AccessListService_GetAccessListOwners_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AccessListServiceServer).GetAccessListOwners(ctx, req.(*GetAccessListOwnersRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AccessListService_UpsertAccessListMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpsertAccessListMemberRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -975,6 +1031,24 @@ func _AccessListService_GetSuggestedAccessLists_Handler(srv interface{}, ctx con
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AccessListService_GetInheritedGrants_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetInheritedGrantsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AccessListServiceServer).GetInheritedGrants(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AccessListService_GetInheritedGrants_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AccessListServiceServer).GetInheritedGrants(ctx, req.(*GetInheritedGrantsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AccessListService_ServiceDesc is the grpc.ServiceDesc for AccessListService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -1030,6 +1104,10 @@ var AccessListService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetAccessListMember",
|
||||
Handler: _AccessListService_GetAccessListMember_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAccessListOwners",
|
||||
Handler: _AccessListService_GetAccessListOwners_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpsertAccessListMember",
|
||||
Handler: _AccessListService_UpsertAccessListMember_Handler,
|
||||
@@ -1078,6 +1156,10 @@ var AccessListService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetSuggestedAccessLists",
|
||||
Handler: _AccessListService_GetSuggestedAccessLists_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetInheritedGrants",
|
||||
Handler: _AccessListService_GetInheritedGrants_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "teleport/accesslist/v1/accesslist_service.proto",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,9 @@ service AccessListService {
|
||||
rpc ListAllAccessListMembers(ListAllAccessListMembersRequest) returns (ListAllAccessListMembersResponse);
|
||||
// GetAccessListMember returns the specified access list member resource.
|
||||
rpc GetAccessListMember(GetAccessListMemberRequest) returns (Member);
|
||||
// GetAccessListOwners returns a list of all owners in an Access List,
|
||||
// including those inherited from nested Access Lists.
|
||||
rpc GetAccessListOwners(GetAccessListOwnersRequest) returns (GetAccessListOwnersResponse);
|
||||
// UpsertAccessListMember creates or updates an access list member resource.
|
||||
rpc UpsertAccessListMember(UpsertAccessListMemberRequest) returns (Member);
|
||||
// UpdateAccessListMember conditionally updates an access list member resource.
|
||||
@@ -89,6 +92,9 @@ service AccessListService {
|
||||
// GetSuggestedAccessLists returns suggested access lists for an access
|
||||
// request.
|
||||
rpc GetSuggestedAccessLists(GetSuggestedAccessListsRequest) returns (GetSuggestedAccessListsResponse);
|
||||
|
||||
// GetInheritedGrants returns the inherited grants for an access list.
|
||||
rpc GetInheritedGrants(GetInheritedGrantsRequest) returns (GetInheritedGrantsResponse);
|
||||
}
|
||||
|
||||
// GetAccessListsRequest is the request for getting all access lists.
|
||||
@@ -117,6 +123,18 @@ message ListAccessListsResponse {
|
||||
string next_token = 2;
|
||||
}
|
||||
|
||||
// GetInheritedGrantsRequest is the request for getting inherited grants.
|
||||
message GetInheritedGrantsRequest {
|
||||
// access_list_id is the ID of the access list to retrieve.
|
||||
string access_list_id = 1;
|
||||
}
|
||||
|
||||
// GetInheritedGrantsResponse is the response for getting inherited grants.
|
||||
message GetInheritedGrantsResponse {
|
||||
// grants is the list of inherited member grants.
|
||||
AccessListGrants grants = 1;
|
||||
}
|
||||
|
||||
// GetAccessListRequest is the request for retrieving an access list.
|
||||
message GetAccessListRequest {
|
||||
// name is the name of the access list to retrieve.
|
||||
@@ -166,6 +184,8 @@ message CountAccessListMembersRequest {
|
||||
message CountAccessListMembersResponse {
|
||||
// count is the number of access list members in the access list.
|
||||
uint32 count = 1;
|
||||
// list_count is the number of access list members of type list in the access list.
|
||||
uint32 list_count = 2;
|
||||
}
|
||||
|
||||
// ListAccessListMembersRequest is the request for getting paginated access list
|
||||
@@ -237,6 +257,21 @@ message GetAccessListMemberRequest {
|
||||
string member_name = 2;
|
||||
}
|
||||
|
||||
// GetAccessListOwnersRequest is the request for getting a list of all owners
|
||||
// in an Access List, including those inherited from nested Access Lists.
|
||||
message GetAccessListOwnersRequest {
|
||||
// access_list is the name of the access list.
|
||||
string access_list = 1;
|
||||
}
|
||||
|
||||
// GetAccessListOwnersResponse is the response for getting a list of all
|
||||
// owners in an Access List, including those inherited from nested Access Lists.
|
||||
message GetAccessListOwnersResponse {
|
||||
// owners is the list of all owners in the Access List, including those
|
||||
// inherited from nested Access Lists.
|
||||
repeated accesslist.v1.AccessListOwner owners = 1;
|
||||
}
|
||||
|
||||
// UpsertAccessListMemberRequest is the request for upserting an access list
|
||||
// member.
|
||||
message UpsertAccessListMemberRequest {
|
||||
|
||||
@@ -20,6 +20,7 @@ import "gogoproto/gogo.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/wrappers.proto";
|
||||
import "teleport/accesslist/v1/accesslist.proto";
|
||||
import "teleport/legacy/types/types.proto";
|
||||
import "teleport/legacy/types/wrappers/wrappers.proto";
|
||||
|
||||
@@ -280,6 +281,10 @@ message AccessListMember {
|
||||
|
||||
// MemberName is the name of the member.
|
||||
string MemberName = 4 [(gogoproto.jsontag) = "member_name,omitempty"];
|
||||
|
||||
// MembershipKind describes the kind of membership, either
|
||||
// `MEMBERSHIP_KIND_USER` or `MEMBERSHIP_KIND_LIST`.
|
||||
teleport.accesslist.v1.MembershipKind membership_kind = 5 [(gogoproto.jsontag) = "membership_kind,omitempty"];
|
||||
}
|
||||
|
||||
// AccessListReviewMembershipRequirementsChanged contains information for when membership requirements change as part of a review.
|
||||
|
||||
@@ -16,6 +16,8 @@ syntax = "proto3";
|
||||
|
||||
package teleport.usageevents.v1;
|
||||
|
||||
import "teleport/accesslist/v1/accesslist.proto";
|
||||
|
||||
option go_package = "github.com/gravitational/teleport/api/gen/proto/go/usageevents/v1;usageeventsv1";
|
||||
|
||||
// UIBannerClickEvent is a usage event sent by the UI when the upgrade
|
||||
@@ -482,6 +484,14 @@ message AccessListMetadata {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
// AccessListMemberMetadata contains common metadata for Access List Member related events.
|
||||
message AccessListMemberMetadata {
|
||||
// name is the name of the member.
|
||||
string name = 1;
|
||||
// membership_kind is the type of membership in the parent access list.
|
||||
teleport.accesslist.v1.MembershipKind membership_kind = 2;
|
||||
}
|
||||
|
||||
// AccessListCreate is an event that is emitted when an access list is created.
|
||||
message AccessListCreate {
|
||||
AccessListMetadata metadata = 1;
|
||||
@@ -500,16 +510,19 @@ message AccessListDelete {
|
||||
// AccessListMemberCreate is an event that is emitted when a member is added to an access list.
|
||||
message AccessListMemberCreate {
|
||||
AccessListMetadata metadata = 1;
|
||||
AccessListMemberMetadata member_metadata = 2;
|
||||
}
|
||||
|
||||
// AccessListMemberCreate is an event that is emitted when a member is updated in an access list.
|
||||
message AccessListMemberUpdate {
|
||||
AccessListMetadata metadata = 1;
|
||||
AccessListMemberMetadata member_metadata = 2;
|
||||
}
|
||||
|
||||
// AccessListMemberDelete is an event that is emitted when a member is removed from an access list.
|
||||
message AccessListMemberDelete {
|
||||
AccessListMetadata metadata = 1;
|
||||
AccessListMemberMetadata member_metadata = 2;
|
||||
}
|
||||
|
||||
// AccessListGrantsToUser is an event that is emitted when access list permissions are granted to a user
|
||||
@@ -518,8 +531,14 @@ message AccessListGrantsToUser {
|
||||
// count_roles_granted is the number of roles granted to a user.
|
||||
int32 count_roles_granted = 1;
|
||||
|
||||
// count_inherited_roles_granted is the number of roles granted to a user inherited from nested access lists.
|
||||
int32 count_inherited_roles_granted = 3;
|
||||
|
||||
// count_traits_granted is the number of traits granted to a user.
|
||||
int32 count_traits_granted = 2;
|
||||
|
||||
// count_inherited_traits_granted is the number of traits granted to a user inherited from nested access lists.
|
||||
int32 count_inherited_traits_granted = 4;
|
||||
}
|
||||
|
||||
// AccessListReviewCreate is an event that is emitted when an access list review is created.
|
||||
|
||||
@@ -47,6 +47,7 @@ func FromMemberProto(msg *accesslistv1.Member, opts ...MemberOption) (*accesslis
|
||||
// Set it to empty as default.
|
||||
// Must provide as options to set it with the provided value.
|
||||
IneligibleStatus: "",
|
||||
MembershipKind: msg.Spec.MembershipKind.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -79,6 +80,11 @@ func ToMemberProto(member *accesslist.AccessListMember) *accesslistv1.Member {
|
||||
ineligibleStatus = accesslistv1.IneligibleStatus(enumVal)
|
||||
}
|
||||
|
||||
var membershipKind accesslistv1.MembershipKind
|
||||
if enumVal, ok := accesslistv1.MembershipKind_value[member.Spec.MembershipKind]; ok {
|
||||
membershipKind = accesslistv1.MembershipKind(enumVal)
|
||||
}
|
||||
|
||||
return &accesslistv1.Member{
|
||||
Header: headerv1.ToResourceHeaderProto(member.ResourceHeader),
|
||||
Spec: &accesslistv1.MemberSpec{
|
||||
@@ -89,6 +95,7 @@ func ToMemberProto(member *accesslist.AccessListMember) *accesslistv1.Member {
|
||||
Reason: member.Spec.Reason,
|
||||
AddedBy: member.Spec.AddedBy,
|
||||
IneligibleStatus: ineligibleStatus,
|
||||
MembershipKind: membershipKind,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+1088
-1053
File diff suppressed because it is too large
Load Diff
+1308
-1248
File diff suppressed because it is too large
Load Diff
+74
-4
@@ -1710,6 +1710,12 @@ export interface AccessListMemberCreateEvent {
|
||||
* @generated from protobuf field: prehog.v1alpha.AccessListMetadata metadata = 2;
|
||||
*/
|
||||
metadata?: AccessListMetadata;
|
||||
/**
|
||||
* member_kind is the type of membership of the created member in the parent access list.
|
||||
*
|
||||
* @generated from protobuf field: string member_kind = 3;
|
||||
*/
|
||||
memberKind: string;
|
||||
}
|
||||
/**
|
||||
* AccessListMemberUpdate is an event that is emitted when a member is updated in an access list.
|
||||
@@ -1727,6 +1733,12 @@ export interface AccessListMemberUpdateEvent {
|
||||
* @generated from protobuf field: prehog.v1alpha.AccessListMetadata metadata = 2;
|
||||
*/
|
||||
metadata?: AccessListMetadata;
|
||||
/**
|
||||
* membership_kind is the type of membership of the updated member in the parent access list.
|
||||
*
|
||||
* @generated from protobuf field: string member_kind = 3;
|
||||
*/
|
||||
memberKind: string;
|
||||
}
|
||||
/**
|
||||
* AccessListMemberDelete is an event that is emitted when a member is removed from an access list.
|
||||
@@ -1744,6 +1756,12 @@ export interface AccessListMemberDeleteEvent {
|
||||
* @generated from protobuf field: prehog.v1alpha.AccessListMetadata metadata = 2;
|
||||
*/
|
||||
metadata?: AccessListMetadata;
|
||||
/**
|
||||
* member_kind is the type of membership of the deleted user in the parent access list.
|
||||
*
|
||||
* @generated from protobuf field: string member_kind = 3;
|
||||
*/
|
||||
memberKind: string;
|
||||
}
|
||||
/**
|
||||
* AccessListGrantsToUser is an event that is emitted when access list permissions are granted to a user
|
||||
@@ -1770,6 +1788,18 @@ export interface AccessListGrantsToUserEvent {
|
||||
* @generated from protobuf field: int32 count_traits_granted = 3;
|
||||
*/
|
||||
countTraitsGranted: number;
|
||||
/**
|
||||
* count_inherited_roles_granted is the number of roles granted to a user inherited from nested access lists.
|
||||
*
|
||||
* @generated from protobuf field: int32 count_inherited_roles_granted = 4;
|
||||
*/
|
||||
countInheritedRolesGranted: number;
|
||||
/**
|
||||
* count_inherited_traits_granted is the number of traits granted to a user inherited from nested access lists.
|
||||
*
|
||||
* @generated from protobuf field: int32 count_inherited_traits_granted = 5;
|
||||
*/
|
||||
countInheritedTraitsGranted: number;
|
||||
}
|
||||
/**
|
||||
* AccessListReviewCreateEvent is an event that is emitted when an access list review is created.
|
||||
@@ -7781,12 +7811,14 @@ class AccessListMemberCreateEvent$Type extends MessageType<AccessListMemberCreat
|
||||
constructor() {
|
||||
super("prehog.v1alpha.AccessListMemberCreateEvent", [
|
||||
{ no: 1, name: "user_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 2, name: "metadata", kind: "message", T: () => AccessListMetadata }
|
||||
{ no: 2, name: "metadata", kind: "message", T: () => AccessListMetadata },
|
||||
{ no: 3, name: "member_kind", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<AccessListMemberCreateEvent>): AccessListMemberCreateEvent {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.userName = "";
|
||||
message.memberKind = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<AccessListMemberCreateEvent>(this, message, value);
|
||||
return message;
|
||||
@@ -7802,6 +7834,9 @@ class AccessListMemberCreateEvent$Type extends MessageType<AccessListMemberCreat
|
||||
case /* prehog.v1alpha.AccessListMetadata metadata */ 2:
|
||||
message.metadata = AccessListMetadata.internalBinaryRead(reader, reader.uint32(), options, message.metadata);
|
||||
break;
|
||||
case /* string member_kind */ 3:
|
||||
message.memberKind = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -7820,6 +7855,9 @@ class AccessListMemberCreateEvent$Type extends MessageType<AccessListMemberCreat
|
||||
/* prehog.v1alpha.AccessListMetadata metadata = 2; */
|
||||
if (message.metadata)
|
||||
AccessListMetadata.internalBinaryWrite(message.metadata, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
/* string member_kind = 3; */
|
||||
if (message.memberKind !== "")
|
||||
writer.tag(3, WireType.LengthDelimited).string(message.memberKind);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -7835,12 +7873,14 @@ class AccessListMemberUpdateEvent$Type extends MessageType<AccessListMemberUpdat
|
||||
constructor() {
|
||||
super("prehog.v1alpha.AccessListMemberUpdateEvent", [
|
||||
{ no: 1, name: "user_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 2, name: "metadata", kind: "message", T: () => AccessListMetadata }
|
||||
{ no: 2, name: "metadata", kind: "message", T: () => AccessListMetadata },
|
||||
{ no: 3, name: "member_kind", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<AccessListMemberUpdateEvent>): AccessListMemberUpdateEvent {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.userName = "";
|
||||
message.memberKind = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<AccessListMemberUpdateEvent>(this, message, value);
|
||||
return message;
|
||||
@@ -7856,6 +7896,9 @@ class AccessListMemberUpdateEvent$Type extends MessageType<AccessListMemberUpdat
|
||||
case /* prehog.v1alpha.AccessListMetadata metadata */ 2:
|
||||
message.metadata = AccessListMetadata.internalBinaryRead(reader, reader.uint32(), options, message.metadata);
|
||||
break;
|
||||
case /* string member_kind */ 3:
|
||||
message.memberKind = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -7874,6 +7917,9 @@ class AccessListMemberUpdateEvent$Type extends MessageType<AccessListMemberUpdat
|
||||
/* prehog.v1alpha.AccessListMetadata metadata = 2; */
|
||||
if (message.metadata)
|
||||
AccessListMetadata.internalBinaryWrite(message.metadata, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
/* string member_kind = 3; */
|
||||
if (message.memberKind !== "")
|
||||
writer.tag(3, WireType.LengthDelimited).string(message.memberKind);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -7889,12 +7935,14 @@ class AccessListMemberDeleteEvent$Type extends MessageType<AccessListMemberDelet
|
||||
constructor() {
|
||||
super("prehog.v1alpha.AccessListMemberDeleteEvent", [
|
||||
{ no: 1, name: "user_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 2, name: "metadata", kind: "message", T: () => AccessListMetadata }
|
||||
{ no: 2, name: "metadata", kind: "message", T: () => AccessListMetadata },
|
||||
{ no: 3, name: "member_kind", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<AccessListMemberDeleteEvent>): AccessListMemberDeleteEvent {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.userName = "";
|
||||
message.memberKind = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<AccessListMemberDeleteEvent>(this, message, value);
|
||||
return message;
|
||||
@@ -7910,6 +7958,9 @@ class AccessListMemberDeleteEvent$Type extends MessageType<AccessListMemberDelet
|
||||
case /* prehog.v1alpha.AccessListMetadata metadata */ 2:
|
||||
message.metadata = AccessListMetadata.internalBinaryRead(reader, reader.uint32(), options, message.metadata);
|
||||
break;
|
||||
case /* string member_kind */ 3:
|
||||
message.memberKind = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -7928,6 +7979,9 @@ class AccessListMemberDeleteEvent$Type extends MessageType<AccessListMemberDelet
|
||||
/* prehog.v1alpha.AccessListMetadata metadata = 2; */
|
||||
if (message.metadata)
|
||||
AccessListMetadata.internalBinaryWrite(message.metadata, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
|
||||
/* string member_kind = 3; */
|
||||
if (message.memberKind !== "")
|
||||
writer.tag(3, WireType.LengthDelimited).string(message.memberKind);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -7944,7 +7998,9 @@ class AccessListGrantsToUserEvent$Type extends MessageType<AccessListGrantsToUse
|
||||
super("prehog.v1alpha.AccessListGrantsToUserEvent", [
|
||||
{ no: 1, name: "user_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 2, name: "count_roles_granted", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
|
||||
{ no: 3, name: "count_traits_granted", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
|
||||
{ no: 3, name: "count_traits_granted", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
|
||||
{ no: 4, name: "count_inherited_roles_granted", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
|
||||
{ no: 5, name: "count_inherited_traits_granted", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<AccessListGrantsToUserEvent>): AccessListGrantsToUserEvent {
|
||||
@@ -7952,6 +8008,8 @@ class AccessListGrantsToUserEvent$Type extends MessageType<AccessListGrantsToUse
|
||||
message.userName = "";
|
||||
message.countRolesGranted = 0;
|
||||
message.countTraitsGranted = 0;
|
||||
message.countInheritedRolesGranted = 0;
|
||||
message.countInheritedTraitsGranted = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<AccessListGrantsToUserEvent>(this, message, value);
|
||||
return message;
|
||||
@@ -7970,6 +8028,12 @@ class AccessListGrantsToUserEvent$Type extends MessageType<AccessListGrantsToUse
|
||||
case /* int32 count_traits_granted */ 3:
|
||||
message.countTraitsGranted = reader.int32();
|
||||
break;
|
||||
case /* int32 count_inherited_roles_granted */ 4:
|
||||
message.countInheritedRolesGranted = reader.int32();
|
||||
break;
|
||||
case /* int32 count_inherited_traits_granted */ 5:
|
||||
message.countInheritedTraitsGranted = reader.int32();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -7991,6 +8055,12 @@ class AccessListGrantsToUserEvent$Type extends MessageType<AccessListGrantsToUse
|
||||
/* int32 count_traits_granted = 3; */
|
||||
if (message.countTraitsGranted !== 0)
|
||||
writer.tag(3, WireType.Varint).int32(message.countTraitsGranted);
|
||||
/* int32 count_inherited_roles_granted = 4; */
|
||||
if (message.countInheritedRolesGranted !== 0)
|
||||
writer.tag(4, WireType.Varint).int32(message.countInheritedRolesGranted);
|
||||
/* int32 count_inherited_traits_granted = 5; */
|
||||
if (message.countInheritedTraitsGranted !== 0)
|
||||
writer.tag(5, WireType.Varint).int32(message.countInheritedTraitsGranted);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
|
||||
+48
-12
@@ -21,6 +21,8 @@
|
||||
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
|
||||
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
|
||||
import { AccessListService } from "./accesslist_service_pb";
|
||||
import type { GetInheritedGrantsResponse } from "./accesslist_service_pb";
|
||||
import type { GetInheritedGrantsRequest } from "./accesslist_service_pb";
|
||||
import type { GetSuggestedAccessListsResponse } from "./accesslist_service_pb";
|
||||
import type { GetSuggestedAccessListsRequest } from "./accesslist_service_pb";
|
||||
import type { AccessRequestPromoteResponse } from "./accesslist_service_pb";
|
||||
@@ -39,6 +41,8 @@ import type { DeleteAllAccessListMembersForAccessListRequest } from "./accesslis
|
||||
import type { DeleteAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import type { UpdateAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import type { UpsertAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import type { GetAccessListOwnersResponse } from "./accesslist_service_pb";
|
||||
import type { GetAccessListOwnersRequest } from "./accesslist_service_pb";
|
||||
import type { Member } from "./accesslist_pb";
|
||||
import type { GetAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import type { ListAllAccessListMembersResponse } from "./accesslist_service_pb";
|
||||
@@ -144,6 +148,13 @@ export interface IAccessListServiceClient {
|
||||
* @generated from protobuf rpc: GetAccessListMember(teleport.accesslist.v1.GetAccessListMemberRequest) returns (teleport.accesslist.v1.Member);
|
||||
*/
|
||||
getAccessListMember(input: GetAccessListMemberRequest, options?: RpcOptions): UnaryCall<GetAccessListMemberRequest, Member>;
|
||||
/**
|
||||
* GetAccessListOwners returns a list of all owners in an Access List,
|
||||
* including those inherited from nested Access Lists.
|
||||
*
|
||||
* @generated from protobuf rpc: GetAccessListOwners(teleport.accesslist.v1.GetAccessListOwnersRequest) returns (teleport.accesslist.v1.GetAccessListOwnersResponse);
|
||||
*/
|
||||
getAccessListOwners(input: GetAccessListOwnersRequest, options?: RpcOptions): UnaryCall<GetAccessListOwnersRequest, GetAccessListOwnersResponse>;
|
||||
/**
|
||||
* UpsertAccessListMember creates or updates an access list member resource.
|
||||
*
|
||||
@@ -224,6 +235,12 @@ export interface IAccessListServiceClient {
|
||||
* @generated from protobuf rpc: GetSuggestedAccessLists(teleport.accesslist.v1.GetSuggestedAccessListsRequest) returns (teleport.accesslist.v1.GetSuggestedAccessListsResponse);
|
||||
*/
|
||||
getSuggestedAccessLists(input: GetSuggestedAccessListsRequest, options?: RpcOptions): UnaryCall<GetSuggestedAccessListsRequest, GetSuggestedAccessListsResponse>;
|
||||
/**
|
||||
* GetInheritedGrants returns the inherited grants for an access list.
|
||||
*
|
||||
* @generated from protobuf rpc: GetInheritedGrants(teleport.accesslist.v1.GetInheritedGrantsRequest) returns (teleport.accesslist.v1.GetInheritedGrantsResponse);
|
||||
*/
|
||||
getInheritedGrants(input: GetInheritedGrantsRequest, options?: RpcOptions): UnaryCall<GetInheritedGrantsRequest, GetInheritedGrantsResponse>;
|
||||
}
|
||||
/**
|
||||
* AccessListService provides CRUD methods for Access List resources.
|
||||
@@ -347,13 +364,23 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
const method = this.methods[11], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<GetAccessListMemberRequest, Member>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* GetAccessListOwners returns a list of all owners in an Access List,
|
||||
* including those inherited from nested Access Lists.
|
||||
*
|
||||
* @generated from protobuf rpc: GetAccessListOwners(teleport.accesslist.v1.GetAccessListOwnersRequest) returns (teleport.accesslist.v1.GetAccessListOwnersResponse);
|
||||
*/
|
||||
getAccessListOwners(input: GetAccessListOwnersRequest, options?: RpcOptions): UnaryCall<GetAccessListOwnersRequest, GetAccessListOwnersResponse> {
|
||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<GetAccessListOwnersRequest, GetAccessListOwnersResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* UpsertAccessListMember creates or updates an access list member resource.
|
||||
*
|
||||
* @generated from protobuf rpc: UpsertAccessListMember(teleport.accesslist.v1.UpsertAccessListMemberRequest) returns (teleport.accesslist.v1.Member);
|
||||
*/
|
||||
upsertAccessListMember(input: UpsertAccessListMemberRequest, options?: RpcOptions): UnaryCall<UpsertAccessListMemberRequest, Member> {
|
||||
const method = this.methods[12], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<UpsertAccessListMemberRequest, Member>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -362,7 +389,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: UpdateAccessListMember(teleport.accesslist.v1.UpdateAccessListMemberRequest) returns (teleport.accesslist.v1.Member);
|
||||
*/
|
||||
updateAccessListMember(input: UpdateAccessListMemberRequest, options?: RpcOptions): UnaryCall<UpdateAccessListMemberRequest, Member> {
|
||||
const method = this.methods[13], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<UpdateAccessListMemberRequest, Member>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -372,7 +399,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: DeleteAccessListMember(teleport.accesslist.v1.DeleteAccessListMemberRequest) returns (google.protobuf.Empty);
|
||||
*/
|
||||
deleteAccessListMember(input: DeleteAccessListMemberRequest, options?: RpcOptions): UnaryCall<DeleteAccessListMemberRequest, Empty> {
|
||||
const method = this.methods[14], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<DeleteAccessListMemberRequest, Empty>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -382,7 +409,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: DeleteAllAccessListMembersForAccessList(teleport.accesslist.v1.DeleteAllAccessListMembersForAccessListRequest) returns (google.protobuf.Empty);
|
||||
*/
|
||||
deleteAllAccessListMembersForAccessList(input: DeleteAllAccessListMembersForAccessListRequest, options?: RpcOptions): UnaryCall<DeleteAllAccessListMembersForAccessListRequest, Empty> {
|
||||
const method = this.methods[15], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[16], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<DeleteAllAccessListMembersForAccessListRequest, Empty>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -392,7 +419,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: DeleteAllAccessListMembers(teleport.accesslist.v1.DeleteAllAccessListMembersRequest) returns (google.protobuf.Empty);
|
||||
*/
|
||||
deleteAllAccessListMembers(input: DeleteAllAccessListMembersRequest, options?: RpcOptions): UnaryCall<DeleteAllAccessListMembersRequest, Empty> {
|
||||
const method = this.methods[16], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[17], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<DeleteAllAccessListMembersRequest, Empty>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -401,7 +428,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: UpsertAccessListWithMembers(teleport.accesslist.v1.UpsertAccessListWithMembersRequest) returns (teleport.accesslist.v1.UpsertAccessListWithMembersResponse);
|
||||
*/
|
||||
upsertAccessListWithMembers(input: UpsertAccessListWithMembersRequest, options?: RpcOptions): UnaryCall<UpsertAccessListWithMembersRequest, UpsertAccessListWithMembersResponse> {
|
||||
const method = this.methods[17], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[18], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<UpsertAccessListWithMembersRequest, UpsertAccessListWithMembersResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -411,7 +438,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: ListAccessListReviews(teleport.accesslist.v1.ListAccessListReviewsRequest) returns (teleport.accesslist.v1.ListAccessListReviewsResponse);
|
||||
*/
|
||||
listAccessListReviews(input: ListAccessListReviewsRequest, options?: RpcOptions): UnaryCall<ListAccessListReviewsRequest, ListAccessListReviewsResponse> {
|
||||
const method = this.methods[18], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[19], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ListAccessListReviewsRequest, ListAccessListReviewsResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -421,7 +448,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: ListAllAccessListReviews(teleport.accesslist.v1.ListAllAccessListReviewsRequest) returns (teleport.accesslist.v1.ListAllAccessListReviewsResponse);
|
||||
*/
|
||||
listAllAccessListReviews(input: ListAllAccessListReviewsRequest, options?: RpcOptions): UnaryCall<ListAllAccessListReviewsRequest, ListAllAccessListReviewsResponse> {
|
||||
const method = this.methods[19], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[20], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<ListAllAccessListReviewsRequest, ListAllAccessListReviewsResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -432,7 +459,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: CreateAccessListReview(teleport.accesslist.v1.CreateAccessListReviewRequest) returns (teleport.accesslist.v1.CreateAccessListReviewResponse);
|
||||
*/
|
||||
createAccessListReview(input: CreateAccessListReviewRequest, options?: RpcOptions): UnaryCall<CreateAccessListReviewRequest, CreateAccessListReviewResponse> {
|
||||
const method = this.methods[20], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[21], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<CreateAccessListReviewRequest, CreateAccessListReviewResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -441,7 +468,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: DeleteAccessListReview(teleport.accesslist.v1.DeleteAccessListReviewRequest) returns (google.protobuf.Empty);
|
||||
*/
|
||||
deleteAccessListReview(input: DeleteAccessListReviewRequest, options?: RpcOptions): UnaryCall<DeleteAccessListReviewRequest, Empty> {
|
||||
const method = this.methods[21], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[22], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<DeleteAccessListReviewRequest, Empty>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -450,7 +477,7 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: AccessRequestPromote(teleport.accesslist.v1.AccessRequestPromoteRequest) returns (teleport.accesslist.v1.AccessRequestPromoteResponse);
|
||||
*/
|
||||
accessRequestPromote(input: AccessRequestPromoteRequest, options?: RpcOptions): UnaryCall<AccessRequestPromoteRequest, AccessRequestPromoteResponse> {
|
||||
const method = this.methods[22], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[23], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<AccessRequestPromoteRequest, AccessRequestPromoteResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
@@ -460,7 +487,16 @@ export class AccessListServiceClient implements IAccessListServiceClient, Servic
|
||||
* @generated from protobuf rpc: GetSuggestedAccessLists(teleport.accesslist.v1.GetSuggestedAccessListsRequest) returns (teleport.accesslist.v1.GetSuggestedAccessListsResponse);
|
||||
*/
|
||||
getSuggestedAccessLists(input: GetSuggestedAccessListsRequest, options?: RpcOptions): UnaryCall<GetSuggestedAccessListsRequest, GetSuggestedAccessListsResponse> {
|
||||
const method = this.methods[23], opt = this._transport.mergeOptions(options);
|
||||
const method = this.methods[24], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<GetSuggestedAccessListsRequest, GetSuggestedAccessListsResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
/**
|
||||
* GetInheritedGrants returns the inherited grants for an access list.
|
||||
*
|
||||
* @generated from protobuf rpc: GetInheritedGrants(teleport.accesslist.v1.GetInheritedGrantsRequest) returns (teleport.accesslist.v1.GetInheritedGrantsResponse);
|
||||
*/
|
||||
getInheritedGrants(input: GetInheritedGrantsRequest, options?: RpcOptions): UnaryCall<GetInheritedGrantsRequest, GetInheritedGrantsResponse> {
|
||||
const method = this.methods[25], opt = this._transport.mergeOptions(options);
|
||||
return stackIntercept<GetInheritedGrantsRequest, GetInheritedGrantsResponse>("unary", this._transport, method, opt, input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { GetInheritedGrantsResponse } from "./accesslist_service_pb";
|
||||
import { GetInheritedGrantsRequest } from "./accesslist_service_pb";
|
||||
import { GetSuggestedAccessListsResponse } from "./accesslist_service_pb";
|
||||
import { GetSuggestedAccessListsRequest } from "./accesslist_service_pb";
|
||||
import { AccessRequestPromoteResponse } from "./accesslist_service_pb";
|
||||
@@ -36,6 +38,8 @@ import { DeleteAllAccessListMembersForAccessListRequest } from "./accesslist_ser
|
||||
import { DeleteAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import { UpdateAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import { UpsertAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import { GetAccessListOwnersResponse } from "./accesslist_service_pb";
|
||||
import { GetAccessListOwnersRequest } from "./accesslist_service_pb";
|
||||
import { Member } from "./accesslist_pb";
|
||||
import { GetAccessListMemberRequest } from "./accesslist_service_pb";
|
||||
import { ListAllAccessListMembersResponse } from "./accesslist_service_pb";
|
||||
@@ -139,6 +143,13 @@ export interface IAccessListService extends grpc.UntypedServiceImplementation {
|
||||
* @generated from protobuf rpc: GetAccessListMember(teleport.accesslist.v1.GetAccessListMemberRequest) returns (teleport.accesslist.v1.Member);
|
||||
*/
|
||||
getAccessListMember: grpc.handleUnaryCall<GetAccessListMemberRequest, Member>;
|
||||
/**
|
||||
* GetAccessListOwners returns a list of all owners in an Access List,
|
||||
* including those inherited from nested Access Lists.
|
||||
*
|
||||
* @generated from protobuf rpc: GetAccessListOwners(teleport.accesslist.v1.GetAccessListOwnersRequest) returns (teleport.accesslist.v1.GetAccessListOwnersResponse);
|
||||
*/
|
||||
getAccessListOwners: grpc.handleUnaryCall<GetAccessListOwnersRequest, GetAccessListOwnersResponse>;
|
||||
/**
|
||||
* UpsertAccessListMember creates or updates an access list member resource.
|
||||
*
|
||||
@@ -219,6 +230,12 @@ export interface IAccessListService extends grpc.UntypedServiceImplementation {
|
||||
* @generated from protobuf rpc: GetSuggestedAccessLists(teleport.accesslist.v1.GetSuggestedAccessListsRequest) returns (teleport.accesslist.v1.GetSuggestedAccessListsResponse);
|
||||
*/
|
||||
getSuggestedAccessLists: grpc.handleUnaryCall<GetSuggestedAccessListsRequest, GetSuggestedAccessListsResponse>;
|
||||
/**
|
||||
* GetInheritedGrants returns the inherited grants for an access list.
|
||||
*
|
||||
* @generated from protobuf rpc: GetInheritedGrants(teleport.accesslist.v1.GetInheritedGrantsRequest) returns (teleport.accesslist.v1.GetInheritedGrantsResponse);
|
||||
*/
|
||||
getInheritedGrants: grpc.handleUnaryCall<GetInheritedGrantsRequest, GetInheritedGrantsResponse>;
|
||||
}
|
||||
/**
|
||||
* @grpc/grpc-js definition for the protobuf service teleport.accesslist.v1.AccessListService.
|
||||
@@ -352,6 +369,16 @@ export const accessListServiceDefinition: grpc.ServiceDefinition<IAccessListServ
|
||||
responseSerialize: value => Buffer.from(Member.toBinary(value)),
|
||||
requestSerialize: value => Buffer.from(GetAccessListMemberRequest.toBinary(value))
|
||||
},
|
||||
getAccessListOwners: {
|
||||
path: "/teleport.accesslist.v1.AccessListService/GetAccessListOwners",
|
||||
originalName: "GetAccessListOwners",
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
responseDeserialize: bytes => GetAccessListOwnersResponse.fromBinary(bytes),
|
||||
requestDeserialize: bytes => GetAccessListOwnersRequest.fromBinary(bytes),
|
||||
responseSerialize: value => Buffer.from(GetAccessListOwnersResponse.toBinary(value)),
|
||||
requestSerialize: value => Buffer.from(GetAccessListOwnersRequest.toBinary(value))
|
||||
},
|
||||
upsertAccessListMember: {
|
||||
path: "/teleport.accesslist.v1.AccessListService/UpsertAccessListMember",
|
||||
originalName: "UpsertAccessListMember",
|
||||
@@ -471,5 +498,15 @@ export const accessListServiceDefinition: grpc.ServiceDefinition<IAccessListServ
|
||||
requestDeserialize: bytes => GetSuggestedAccessListsRequest.fromBinary(bytes),
|
||||
responseSerialize: value => Buffer.from(GetSuggestedAccessListsResponse.toBinary(value)),
|
||||
requestSerialize: value => Buffer.from(GetSuggestedAccessListsRequest.toBinary(value))
|
||||
},
|
||||
getInheritedGrants: {
|
||||
path: "/teleport.accesslist.v1.AccessListService/GetInheritedGrants",
|
||||
originalName: "GetInheritedGrants",
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
responseDeserialize: bytes => GetInheritedGrantsResponse.fromBinary(bytes),
|
||||
requestDeserialize: bytes => GetInheritedGrantsRequest.fromBinary(bytes),
|
||||
responseSerialize: value => Buffer.from(GetInheritedGrantsResponse.toBinary(value)),
|
||||
requestSerialize: value => Buffer.from(GetInheritedGrantsRequest.toBinary(value))
|
||||
}
|
||||
};
|
||||
|
||||
+262
-2
@@ -32,7 +32,9 @@ import { MessageType } from "@protobuf-ts/runtime";
|
||||
import { AccessRequestV3 } from "../../legacy/types/types_pb";
|
||||
import { Timestamp } from "../../../google/protobuf/timestamp_pb";
|
||||
import { Review } from "./accesslist_pb";
|
||||
import { AccessListOwner } from "./accesslist_pb";
|
||||
import { Member } from "./accesslist_pb";
|
||||
import { AccessListGrants } from "./accesslist_pb";
|
||||
import { AccessList } from "./accesslist_pb";
|
||||
/**
|
||||
* GetAccessListsRequest is the request for getting all access lists.
|
||||
@@ -92,6 +94,32 @@ export interface ListAccessListsResponse {
|
||||
*/
|
||||
nextToken: string;
|
||||
}
|
||||
/**
|
||||
* GetInheritedGrantsRequest is the request for getting inherited grants.
|
||||
*
|
||||
* @generated from protobuf message teleport.accesslist.v1.GetInheritedGrantsRequest
|
||||
*/
|
||||
export interface GetInheritedGrantsRequest {
|
||||
/**
|
||||
* access_list_id is the ID of the access list to retrieve.
|
||||
*
|
||||
* @generated from protobuf field: string access_list_id = 1;
|
||||
*/
|
||||
accessListId: string;
|
||||
}
|
||||
/**
|
||||
* GetInheritedGrantsResponse is the response for getting inherited grants.
|
||||
*
|
||||
* @generated from protobuf message teleport.accesslist.v1.GetInheritedGrantsResponse
|
||||
*/
|
||||
export interface GetInheritedGrantsResponse {
|
||||
/**
|
||||
* grants is the list of inherited member grants.
|
||||
*
|
||||
* @generated from protobuf field: teleport.accesslist.v1.AccessListGrants grants = 1;
|
||||
*/
|
||||
grants?: AccessListGrants;
|
||||
}
|
||||
/**
|
||||
* GetAccessListRequest is the request for retrieving an access list.
|
||||
*
|
||||
@@ -198,6 +226,12 @@ export interface CountAccessListMembersResponse {
|
||||
* @generated from protobuf field: uint32 count = 1;
|
||||
*/
|
||||
count: number;
|
||||
/**
|
||||
* list_count is the number of access list members of type list in the access list.
|
||||
*
|
||||
* @generated from protobuf field: uint32 list_count = 2;
|
||||
*/
|
||||
listCount: number;
|
||||
}
|
||||
/**
|
||||
* ListAccessListMembersRequest is the request for getting paginated access list
|
||||
@@ -345,6 +379,35 @@ export interface GetAccessListMemberRequest {
|
||||
*/
|
||||
memberName: string;
|
||||
}
|
||||
/**
|
||||
* GetAccessListOwnersRequest is the request for getting a list of all owners
|
||||
* in an Access List, including those inherited from nested Access Lists.
|
||||
*
|
||||
* @generated from protobuf message teleport.accesslist.v1.GetAccessListOwnersRequest
|
||||
*/
|
||||
export interface GetAccessListOwnersRequest {
|
||||
/**
|
||||
* access_list is the name of the access list.
|
||||
*
|
||||
* @generated from protobuf field: string access_list = 1;
|
||||
*/
|
||||
accessList: string;
|
||||
}
|
||||
/**
|
||||
* GetAccessListOwnersResponse is the response for getting a list of all
|
||||
* owners in an Access List, including those inherited from nested Access Lists.
|
||||
*
|
||||
* @generated from protobuf message teleport.accesslist.v1.GetAccessListOwnersResponse
|
||||
*/
|
||||
export interface GetAccessListOwnersResponse {
|
||||
/**
|
||||
* owners is the list of all owners in the Access List, including those
|
||||
* inherited from nested Access Lists.
|
||||
*
|
||||
* @generated from protobuf field: repeated teleport.accesslist.v1.AccessListOwner owners = 1;
|
||||
*/
|
||||
owners: AccessListOwner[];
|
||||
}
|
||||
/**
|
||||
* UpsertAccessListMemberRequest is the request for upserting an access list
|
||||
* member.
|
||||
@@ -806,6 +869,99 @@ class ListAccessListsResponse$Type extends MessageType<ListAccessListsResponse>
|
||||
*/
|
||||
export const ListAccessListsResponse = new ListAccessListsResponse$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class GetInheritedGrantsRequest$Type extends MessageType<GetInheritedGrantsRequest> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.GetInheritedGrantsRequest", [
|
||||
{ no: 1, name: "access_list_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<GetInheritedGrantsRequest>): GetInheritedGrantsRequest {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.accessListId = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<GetInheritedGrantsRequest>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetInheritedGrantsRequest): GetInheritedGrantsRequest {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* string access_list_id */ 1:
|
||||
message.accessListId = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: GetInheritedGrantsRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* string access_list_id = 1; */
|
||||
if (message.accessListId !== "")
|
||||
writer.tag(1, WireType.LengthDelimited).string(message.accessListId);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message teleport.accesslist.v1.GetInheritedGrantsRequest
|
||||
*/
|
||||
export const GetInheritedGrantsRequest = new GetInheritedGrantsRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class GetInheritedGrantsResponse$Type extends MessageType<GetInheritedGrantsResponse> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.GetInheritedGrantsResponse", [
|
||||
{ no: 1, name: "grants", kind: "message", T: () => AccessListGrants }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<GetInheritedGrantsResponse>): GetInheritedGrantsResponse {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<GetInheritedGrantsResponse>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetInheritedGrantsResponse): GetInheritedGrantsResponse {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* teleport.accesslist.v1.AccessListGrants grants */ 1:
|
||||
message.grants = AccessListGrants.internalBinaryRead(reader, reader.uint32(), options, message.grants);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: GetInheritedGrantsResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* teleport.accesslist.v1.AccessListGrants grants = 1; */
|
||||
if (message.grants)
|
||||
AccessListGrants.internalBinaryWrite(message.grants, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message teleport.accesslist.v1.GetInheritedGrantsResponse
|
||||
*/
|
||||
export const GetInheritedGrantsResponse = new GetInheritedGrantsResponse$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class GetAccessListRequest$Type extends MessageType<GetAccessListRequest> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.GetAccessListRequest", [
|
||||
@@ -1139,12 +1295,14 @@ export const CountAccessListMembersRequest = new CountAccessListMembersRequest$T
|
||||
class CountAccessListMembersResponse$Type extends MessageType<CountAccessListMembersResponse> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.CountAccessListMembersResponse", [
|
||||
{ no: 1, name: "count", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
|
||||
{ no: 1, name: "count", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 2, name: "list_count", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<CountAccessListMembersResponse>): CountAccessListMembersResponse {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.count = 0;
|
||||
message.listCount = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<CountAccessListMembersResponse>(this, message, value);
|
||||
return message;
|
||||
@@ -1157,6 +1315,9 @@ class CountAccessListMembersResponse$Type extends MessageType<CountAccessListMem
|
||||
case /* uint32 count */ 1:
|
||||
message.count = reader.uint32();
|
||||
break;
|
||||
case /* uint32 list_count */ 2:
|
||||
message.listCount = reader.uint32();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -1172,6 +1333,9 @@ class CountAccessListMembersResponse$Type extends MessageType<CountAccessListMem
|
||||
/* uint32 count = 1; */
|
||||
if (message.count !== 0)
|
||||
writer.tag(1, WireType.Varint).uint32(message.count);
|
||||
/* uint32 list_count = 2; */
|
||||
if (message.listCount !== 0)
|
||||
writer.tag(2, WireType.Varint).uint32(message.listCount);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -1574,6 +1738,100 @@ class GetAccessListMemberRequest$Type extends MessageType<GetAccessListMemberReq
|
||||
*/
|
||||
export const GetAccessListMemberRequest = new GetAccessListMemberRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class GetAccessListOwnersRequest$Type extends MessageType<GetAccessListOwnersRequest> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.GetAccessListOwnersRequest", [
|
||||
{ no: 1, name: "access_list", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<GetAccessListOwnersRequest>): GetAccessListOwnersRequest {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.accessList = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<GetAccessListOwnersRequest>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetAccessListOwnersRequest): GetAccessListOwnersRequest {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* string access_list */ 1:
|
||||
message.accessList = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: GetAccessListOwnersRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* string access_list = 1; */
|
||||
if (message.accessList !== "")
|
||||
writer.tag(1, WireType.LengthDelimited).string(message.accessList);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message teleport.accesslist.v1.GetAccessListOwnersRequest
|
||||
*/
|
||||
export const GetAccessListOwnersRequest = new GetAccessListOwnersRequest$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class GetAccessListOwnersResponse$Type extends MessageType<GetAccessListOwnersResponse> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.GetAccessListOwnersResponse", [
|
||||
{ no: 1, name: "owners", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => AccessListOwner }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<GetAccessListOwnersResponse>): GetAccessListOwnersResponse {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.owners = [];
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<GetAccessListOwnersResponse>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GetAccessListOwnersResponse): GetAccessListOwnersResponse {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* repeated teleport.accesslist.v1.AccessListOwner owners */ 1:
|
||||
message.owners.push(AccessListOwner.internalBinaryRead(reader, reader.uint32(), options));
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: GetAccessListOwnersResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* repeated teleport.accesslist.v1.AccessListOwner owners = 1; */
|
||||
for (let i = 0; i < message.owners.length; i++)
|
||||
AccessListOwner.internalBinaryWrite(message.owners[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message teleport.accesslist.v1.GetAccessListOwnersResponse
|
||||
*/
|
||||
export const GetAccessListOwnersResponse = new GetAccessListOwnersResponse$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class UpsertAccessListMemberRequest$Type extends MessageType<UpsertAccessListMemberRequest> {
|
||||
constructor() {
|
||||
super("teleport.accesslist.v1.UpsertAccessListMemberRequest", [
|
||||
@@ -2394,6 +2652,7 @@ export const AccessListService = new ServiceType("teleport.accesslist.v1.AccessL
|
||||
{ name: "ListAccessListMembers", options: {}, I: ListAccessListMembersRequest, O: ListAccessListMembersResponse },
|
||||
{ name: "ListAllAccessListMembers", options: {}, I: ListAllAccessListMembersRequest, O: ListAllAccessListMembersResponse },
|
||||
{ name: "GetAccessListMember", options: {}, I: GetAccessListMemberRequest, O: Member },
|
||||
{ name: "GetAccessListOwners", options: {}, I: GetAccessListOwnersRequest, O: GetAccessListOwnersResponse },
|
||||
{ name: "UpsertAccessListMember", options: {}, I: UpsertAccessListMemberRequest, O: Member },
|
||||
{ name: "UpdateAccessListMember", options: {}, I: UpdateAccessListMemberRequest, O: Member },
|
||||
{ name: "DeleteAccessListMember", options: {}, I: DeleteAccessListMemberRequest, O: Empty },
|
||||
@@ -2405,5 +2664,6 @@ export const AccessListService = new ServiceType("teleport.accesslist.v1.AccessL
|
||||
{ name: "CreateAccessListReview", options: {}, I: CreateAccessListReviewRequest, O: CreateAccessListReviewResponse },
|
||||
{ name: "DeleteAccessListReview", options: {}, I: DeleteAccessListReviewRequest, O: Empty },
|
||||
{ name: "AccessRequestPromote", options: {}, I: AccessRequestPromoteRequest, O: AccessRequestPromoteResponse },
|
||||
{ name: "GetSuggestedAccessLists", options: {}, I: GetSuggestedAccessListsRequest, O: GetSuggestedAccessListsResponse }
|
||||
{ name: "GetSuggestedAccessLists", options: {}, I: GetSuggestedAccessListsRequest, O: GetSuggestedAccessListsResponse },
|
||||
{ name: "GetInheritedGrants", options: {}, I: GetInheritedGrantsRequest, O: GetInheritedGrantsResponse }
|
||||
]);
|
||||
|
||||
@@ -249,10 +249,25 @@ func (a *App) getRecipientsRequiringReminders(ctx context.Context, accessList *a
|
||||
func (a *App) fetchRecipients(ctx context.Context, accessList *accesslist.AccessList, now, notificationStart time.Time) map[string]common.Recipient {
|
||||
log := logger.Get(ctx)
|
||||
|
||||
allRecipients := make(map[string]common.Recipient, len(accessList.Spec.Owners))
|
||||
var allOwners []*accesslist.Owner
|
||||
|
||||
allOwners, err := a.apiClient.GetAccessListOwners(ctx, accessList.GetName())
|
||||
if err != nil {
|
||||
// TODO(kiosion): Remove in v18; protecting against server not having `GetAccessListOwners` func.
|
||||
if trace.IsNotImplemented(err) {
|
||||
log.WithError(err).Warnf("Error getting nested owners for access list '%v', continuing with only explicit owners", accessList.GetName())
|
||||
for _, owner := range accessList.Spec.Owners {
|
||||
allOwners = append(allOwners, &owner)
|
||||
}
|
||||
} else {
|
||||
log.WithError(err).Errorf("Error getting owners for access list '%v'", accessList.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
allRecipients := make(map[string]common.Recipient, len(allOwners))
|
||||
|
||||
// Get the owners from the bot as recipients.
|
||||
for _, owner := range accessList.Spec.Owners {
|
||||
for _, owner := range allOwners {
|
||||
recipient, err := a.bot.FetchRecipient(ctx, owner.Name)
|
||||
if err != nil {
|
||||
log.Debugf("error getting recipient %s", owner.Name)
|
||||
|
||||
@@ -64,6 +64,10 @@ func (w *wrappedClient) ListAccessLists(ctx context.Context, pageSize int, pageT
|
||||
return w.Client.AccessListClient().ListAccessLists(ctx, pageSize, pageToken)
|
||||
}
|
||||
|
||||
func (w *wrappedClient) GetAccessListOwners(ctx context.Context, accessListName string) ([]*accesslist.Owner, error) {
|
||||
return w.Client.AccessListClient().GetAccessListOwners(ctx, accessListName)
|
||||
}
|
||||
|
||||
// ListAccessMonitoringRulesWithFilter lists current access monitoring rules.
|
||||
func (w *wrappedClient) ListAccessMonitoringRulesWithFilter(ctx context.Context, pageSize int, pageToken string, subjects []string, notificationName string) ([]*accessmonitoringrulesv1.AccessMonitoringRule, string, error) {
|
||||
return w.Client.AccessMonitoringRulesClient().ListAccessMonitoringRulesWithFilter(ctx, pageSize, pageToken, subjects, notificationName)
|
||||
|
||||
@@ -42,4 +42,5 @@ type Client interface {
|
||||
ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error)
|
||||
ListAccessLists(context.Context, int, string) ([]*accesslist.AccessList, string, error)
|
||||
ListAccessMonitoringRulesWithFilter(ctx context.Context, pageSize int, pageToken string, subjects []string, notificationName string) ([]*accessmonitoringrulesv1.AccessMonitoringRule, string, error)
|
||||
GetAccessListOwners(ctx context.Context, accessListName string) ([]*accesslist.Owner, error)
|
||||
}
|
||||
|
||||
@@ -1564,11 +1564,17 @@ func TestUpdateAccessRequestWithAdditionalReviewers(t *testing.T) {
|
||||
return req
|
||||
}
|
||||
|
||||
mustAccessList := func(name string, owners ...string) *accesslist.AccessList {
|
||||
type testAccessListOwner struct {
|
||||
name string
|
||||
kind string
|
||||
}
|
||||
|
||||
mustAccessListWithMembershipKind := func(name string, owners ...testAccessListOwner) *accesslist.AccessList {
|
||||
ownersSpec := make([]accesslist.Owner, len(owners))
|
||||
for i, owner := range owners {
|
||||
ownersSpec[i] = accesslist.Owner{
|
||||
Name: owner,
|
||||
Name: owner.name,
|
||||
MembershipKind: owner.kind,
|
||||
}
|
||||
}
|
||||
accessList, err := accesslist.NewAccessList(header.Metadata{
|
||||
@@ -1587,10 +1593,22 @@ func TestUpdateAccessRequestWithAdditionalReviewers(t *testing.T) {
|
||||
return accessList
|
||||
}
|
||||
|
||||
mustAccessList := func(name string, owners ...string) *accesslist.AccessList {
|
||||
ownersStruct := make([]testAccessListOwner, 0, len(owners))
|
||||
for _, owner := range owners {
|
||||
ownersStruct = append(ownersStruct, testAccessListOwner{owner, accesslist.MembershipKindUser})
|
||||
}
|
||||
return mustAccessListWithMembershipKind(name, ownersStruct...)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req types.AccessRequest
|
||||
accessLists []*accesslist.AccessList
|
||||
accessListMembers []struct {
|
||||
Header header.Metadata
|
||||
Spec accesslist.AccessListMemberSpec
|
||||
}
|
||||
promotions *types.AccessRequestAllowedPromotions
|
||||
expectedReviewers []string
|
||||
}{
|
||||
@@ -1615,6 +1633,94 @@ func TestUpdateAccessRequestWithAdditionalReviewers(t *testing.T) {
|
||||
},
|
||||
expectedReviewers: []string{"rev1", "rev2", "owner1", "owner2", "owner3"},
|
||||
},
|
||||
{
|
||||
name: "with ownership through nested list",
|
||||
req: mustRequest("rev1"),
|
||||
accessLists: []*accesslist.AccessList{
|
||||
mustAccessList("nested1", "owner1"),
|
||||
mustAccessListWithMembershipKind(
|
||||
"nested",
|
||||
testAccessListOwner{"owner1", accesslist.MembershipKindUser},
|
||||
testAccessListOwner{"nested1", accesslist.MembershipKindList},
|
||||
),
|
||||
mustAccessListWithMembershipKind(
|
||||
"root",
|
||||
testAccessListOwner{"owner1", accesslist.MembershipKindUser},
|
||||
testAccessListOwner{"nested", accesslist.MembershipKindList},
|
||||
),
|
||||
},
|
||||
accessListMembers: []struct {
|
||||
Header header.Metadata
|
||||
Spec accesslist.AccessListMemberSpec
|
||||
}{
|
||||
{
|
||||
Header: header.Metadata{
|
||||
Name: "nested",
|
||||
},
|
||||
Spec: accesslist.AccessListMemberSpec{
|
||||
AccessList: "root",
|
||||
Name: "nested",
|
||||
Joined: clock.Now().UTC(),
|
||||
Expires: clock.Now().UTC().Add(24 * time.Hour),
|
||||
Reason: "because",
|
||||
AddedBy: "owner1",
|
||||
MembershipKind: accesslist.MembershipKindList,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: header.Metadata{
|
||||
Name: "nested1",
|
||||
},
|
||||
Spec: accesslist.AccessListMemberSpec{
|
||||
AccessList: "nested",
|
||||
Name: "nested1",
|
||||
Joined: clock.Now().UTC(),
|
||||
Expires: clock.Now().UTC().Add(24 * time.Hour),
|
||||
Reason: "because",
|
||||
AddedBy: "owner1",
|
||||
MembershipKind: accesslist.MembershipKindList,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: header.Metadata{
|
||||
Name: "owner2",
|
||||
},
|
||||
Spec: accesslist.AccessListMemberSpec{
|
||||
AccessList: "nested",
|
||||
Name: "owner2",
|
||||
Joined: clock.Now().UTC(),
|
||||
Expires: clock.Now().UTC().Add(24 * time.Hour),
|
||||
Reason: "because",
|
||||
AddedBy: "owner1",
|
||||
MembershipKind: accesslist.MembershipKindUser,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: header.Metadata{
|
||||
Name: "owner3",
|
||||
},
|
||||
Spec: accesslist.AccessListMemberSpec{
|
||||
AccessList: "nested1",
|
||||
Name: "owner3",
|
||||
Joined: clock.Now().UTC(),
|
||||
Expires: clock.Now().UTC().Add(24 * time.Hour),
|
||||
Reason: "because",
|
||||
AddedBy: "owner1",
|
||||
MembershipKind: accesslist.MembershipKindUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
promotions: &types.AccessRequestAllowedPromotions{
|
||||
Promotions: []*types.AccessRequestAllowedPromotion{
|
||||
{AccessListName: "root"},
|
||||
{AccessListName: "nested"},
|
||||
},
|
||||
},
|
||||
// owner1 is owner of 'root', should be included
|
||||
// owner2 is member of 'nested', which is owner of 'root', should be included via inheritance
|
||||
// owner3 is member of 'nested1', which is member of 'nested', which is owner of 'root', should be included via two levels of inheritance
|
||||
expectedReviewers: []string{"rev1", "owner1", "owner2", "owner3"},
|
||||
},
|
||||
{
|
||||
name: "no promotions",
|
||||
req: mustRequest("rev1", "rev2"),
|
||||
@@ -1644,6 +1750,14 @@ func TestUpdateAccessRequestWithAdditionalReviewers(t *testing.T) {
|
||||
_, err = accessLists.UpsertAccessList(ctx, accessList)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if test.accessListMembers != nil {
|
||||
for _, memberData := range test.accessListMembers {
|
||||
member, err := accesslist.NewAccessListMember(memberData.Header, memberData.Spec)
|
||||
require.NoError(t, err)
|
||||
_, err = accessLists.UpsertAccessListMember(ctx, member)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
req := test.req.Copy()
|
||||
updateAccessRequestWithAdditionalReviewers(ctx, req, accessLists, test.promotions)
|
||||
|
||||
+3
-3
@@ -5305,13 +5305,13 @@ func updateAccessRequestWithAdditionalReviewers(ctx context.Context, req types.A
|
||||
|
||||
// Iterate through the promotions, adding the owners of the corresponding access lists as reviewers.
|
||||
for _, promotion := range promotions.Promotions {
|
||||
accessList, err := accessLists.GetAccessList(ctx, promotion.AccessListName)
|
||||
allOwners, err := accessLists.GetAccessListOwners(ctx, promotion.AccessListName)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Failed to get access list, skipping additional reviewers")
|
||||
log.WithError(err).Warnf("Failed to get nested access list owners for %v, skipping additional reviewers", promotion.AccessListName)
|
||||
break
|
||||
}
|
||||
|
||||
for _, owner := range accessList.GetOwners() {
|
||||
for _, owner := range allOwners {
|
||||
additionalReviewers[owner.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1164,7 +1164,7 @@ type Cache interface {
|
||||
GetAccessList(context.Context, string) (*accesslist.AccessList, error)
|
||||
|
||||
// CountAccessListMembers will count all access list members.
|
||||
CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error)
|
||||
CountAccessListMembers(ctx context.Context, accessListName string) (users uint32, lists uint32, err error)
|
||||
// ListAccessListMembers returns a paginated list of all access list members.
|
||||
// May return a DynamicAccessListError if the requested access list has an
|
||||
// implicit member list and the underlying implementation does not have
|
||||
|
||||
@@ -32,9 +32,9 @@ import (
|
||||
"github.com/gravitational/teleport/api/types/header"
|
||||
"github.com/gravitational/teleport/api/types/userloginstate"
|
||||
"github.com/gravitational/teleport/api/utils"
|
||||
"github.com/gravitational/teleport/lib/accesslists"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
)
|
||||
|
||||
// AccessListsAndLockGetter is an interface for retrieving access lists and locks.
|
||||
@@ -97,12 +97,11 @@ func (g *GeneratorConfig) CheckAndSetDefaults() error {
|
||||
|
||||
// Generator will generate a user login state from a user.
|
||||
type Generator struct {
|
||||
log *logrus.Entry
|
||||
accessLists AccessListsAndLockGetter
|
||||
access services.Access
|
||||
usageEvents UsageEventsClient
|
||||
memberChecker *services.AccessListMembershipChecker
|
||||
clock clockwork.Clock
|
||||
log *logrus.Entry
|
||||
accessLists AccessListsAndLockGetter
|
||||
access services.Access
|
||||
usageEvents UsageEventsClient
|
||||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
// NewGenerator creates a new user login state generator.
|
||||
@@ -112,12 +111,11 @@ func NewGenerator(config GeneratorConfig) (*Generator, error) {
|
||||
}
|
||||
|
||||
return &Generator{
|
||||
log: config.Log,
|
||||
accessLists: config.AccessLists,
|
||||
access: config.Access,
|
||||
usageEvents: config.UsageEvents,
|
||||
memberChecker: services.NewAccessListMembershipChecker(config.Clock, config.AccessLists, config.Access),
|
||||
clock: config.Clock,
|
||||
log: config.Log,
|
||||
accessLists: config.AccessLists,
|
||||
access: config.Access,
|
||||
usageEvents: config.UsageEvents,
|
||||
clock: config.Clock,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -151,7 +149,8 @@ func (g *Generator) Generate(ctx context.Context, user types.User) (*userloginst
|
||||
}
|
||||
|
||||
// Generate the user login state.
|
||||
if err := g.addAccessListsToState(ctx, user, uls); err != nil {
|
||||
inheritedRoles, inheritedTraits, err := g.addAccessListsToState(ctx, user, uls)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -162,7 +161,7 @@ func (g *Generator) Generate(ctx context.Context, user types.User) (*userloginst
|
||||
|
||||
if g.usageEvents != nil {
|
||||
// Emit the usage event metadata.
|
||||
if err := g.emitUsageEvent(ctx, user, uls); err != nil {
|
||||
if err := g.emitUsageEvent(ctx, user, uls, inheritedRoles, inheritedTraits); err != nil {
|
||||
g.log.Debug("Error emitting usage event during user login state generation, skipping")
|
||||
}
|
||||
}
|
||||
@@ -170,35 +169,54 @@ func (g *Generator) Generate(ctx context.Context, user types.User) (*userloginst
|
||||
return uls, nil
|
||||
}
|
||||
|
||||
// addAccessListsToState will added the user's applicable access lists to the user login state.
|
||||
func (g *Generator) addAccessListsToState(ctx context.Context, user types.User, state *userloginstate.UserLoginState) error {
|
||||
// addAccessListsToState will add the user's applicable access lists to the user login state,
|
||||
// returning any inherited roles and traits.
|
||||
func (g *Generator) addAccessListsToState(ctx context.Context, user types.User, state *userloginstate.UserLoginState) ([]string, map[string][]string, error) {
|
||||
accessLists, err := g.accessLists.GetAccessLists(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Create an identity for testing membership to access lists.
|
||||
identity := tlsca.Identity{
|
||||
Username: user.GetName(),
|
||||
Groups: user.GetRoles(),
|
||||
Traits: user.GetTraits(),
|
||||
UserType: user.GetUserType(),
|
||||
}
|
||||
var allInheritedRoles []string
|
||||
allInheritedTraits := make(map[string][]string)
|
||||
|
||||
for _, accessList := range accessLists {
|
||||
if err := services.IsAccessListOwner(identity, accessList); err == nil {
|
||||
g.grantRolesAndTraits(identity, accessList.Spec.OwnerGrants, state)
|
||||
// Grants are inherited if the user is a member of the access list, explicitly or via inheritance.
|
||||
membershipKind, err := accesslists.IsAccessListMember(ctx, user, accessList, g.accessLists, g.accessLists, g.clock)
|
||||
if err != nil {
|
||||
g.log.WithError(err).Warn("checking access list membership")
|
||||
}
|
||||
|
||||
if err := g.memberChecker.IsAccessListMember(ctx, identity, accessList); err == nil {
|
||||
g.grantRolesAndTraits(identity, accessList.Spec.Grants, state)
|
||||
if err == nil && membershipKind != accesslists.MembershipOrOwnershipTypeNone {
|
||||
g.grantRolesAndTraits(accessList.Spec.Grants, state)
|
||||
if membershipKind == accesslists.MembershipOrOwnershipTypeInherited {
|
||||
allInheritedRoles = append(allInheritedRoles, accessList.Spec.Grants.Roles...)
|
||||
for k, values := range accessList.Spec.Grants.Traits {
|
||||
allInheritedTraits[k] = append(allInheritedTraits[k], values...)
|
||||
}
|
||||
}
|
||||
}
|
||||
// OwnerGrants are inherited if the user is an owner of the access list, explicitly or via inheritance.
|
||||
ownershipType, err := accesslists.IsAccessListOwner(ctx, user, accessList, g.accessLists, g.accessLists, g.clock)
|
||||
if err != nil {
|
||||
g.log.WithError(err).Warn("checking access list ownership")
|
||||
}
|
||||
if err == nil && ownershipType != accesslists.MembershipOrOwnershipTypeNone {
|
||||
g.grantRolesAndTraits(accessList.Spec.OwnerGrants, state)
|
||||
if ownershipType == accesslists.MembershipOrOwnershipTypeInherited {
|
||||
allInheritedRoles = append(allInheritedRoles, accessList.Spec.OwnerGrants.Roles...)
|
||||
for k, values := range accessList.Spec.OwnerGrants.Traits {
|
||||
allInheritedTraits[k] = append(allInheritedTraits[k], values...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return allInheritedRoles, allInheritedTraits, nil
|
||||
}
|
||||
|
||||
func (g *Generator) grantRolesAndTraits(identity tlsca.Identity, grants accesslist.Grants, state *userloginstate.UserLoginState) {
|
||||
// grantRolesAndTraits will append the roles and traits from the provided Grants to the UserLoginState,
|
||||
// returning inherited roles and traits if membershipOrOwnershipType is inherited.
|
||||
func (g *Generator) grantRolesAndTraits(grants accesslist.Grants, state *userloginstate.UserLoginState) {
|
||||
state.Spec.Roles = append(state.Spec.Roles, grants.Roles...)
|
||||
|
||||
if state.Spec.Traits == nil && len(grants.Traits) > 0 {
|
||||
@@ -214,7 +232,6 @@ func (g *Generator) grantRolesAndTraits(identity tlsca.Identity, grants accessli
|
||||
func (g *Generator) postProcess(ctx context.Context, state *userloginstate.UserLoginState) error {
|
||||
// Deduplicate roles and traits
|
||||
state.Spec.Roles = utils.Deduplicate(state.Spec.Roles)
|
||||
|
||||
for k, v := range state.Spec.Traits {
|
||||
state.Spec.Traits[k] = utils.Deduplicate(v)
|
||||
}
|
||||
@@ -225,6 +242,7 @@ func (g *Generator) postProcess(ctx context.Context, state *userloginstate.UserL
|
||||
}
|
||||
|
||||
// Make sure all the roles exist. If they don't, error out.
|
||||
// Since InheritedRoles are always a subset of Roles, we don't need to check them.
|
||||
var existingRoles []string
|
||||
for _, role := range state.Spec.Roles {
|
||||
_, err := g.access.GetRole(ctx, role)
|
||||
@@ -240,7 +258,7 @@ func (g *Generator) postProcess(ctx context.Context, state *userloginstate.UserL
|
||||
}
|
||||
|
||||
// emitUsageEvent will emit the usage event for user state generation.
|
||||
func (g *Generator) emitUsageEvent(ctx context.Context, user types.User, state *userloginstate.UserLoginState) error {
|
||||
func (g *Generator) emitUsageEvent(ctx context.Context, user types.User, state *userloginstate.UserLoginState, inheritedRoles []string, inheritedTraits map[string][]string) error {
|
||||
staticRoleCount := len(user.GetRoles())
|
||||
staticTraitCount := 0
|
||||
for _, values := range user.GetTraits() {
|
||||
@@ -253,17 +271,30 @@ func (g *Generator) emitUsageEvent(ctx context.Context, user types.User, state *
|
||||
stateTraitCount += len(values)
|
||||
}
|
||||
|
||||
inheritedRoles = utils.Deduplicate(inheritedRoles)
|
||||
for k, v := range inheritedTraits {
|
||||
inheritedTraits[k] = utils.Deduplicate(v)
|
||||
}
|
||||
|
||||
countInheritedRolesGranted := len(inheritedRoles)
|
||||
countInheritedTraitsGranted := 0
|
||||
for _, values := range inheritedTraits {
|
||||
countInheritedTraitsGranted += len(values)
|
||||
}
|
||||
|
||||
countRolesGranted := stateRoleCount - staticRoleCount
|
||||
countTraitsGranted := stateTraitCount - staticTraitCount
|
||||
|
||||
// No roles or traits were granted, so skip emitting the event.
|
||||
if countRolesGranted == 0 && countTraitsGranted == 0 {
|
||||
// No roles or traits were granted or inherited, so skip emitting the event.
|
||||
if countRolesGranted+countTraitsGranted+countInheritedRolesGranted+countInheritedTraitsGranted == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
grantsToUser := &usageeventsv1.AccessListGrantsToUser{
|
||||
CountRolesGranted: int32(countRolesGranted),
|
||||
CountTraitsGranted: int32(countTraitsGranted),
|
||||
CountRolesGranted: int32(countRolesGranted),
|
||||
CountTraitsGranted: int32(countTraitsGranted),
|
||||
CountInheritedRolesGranted: int32(countInheritedRolesGranted),
|
||||
CountInheritedTraitsGranted: int32(countInheritedTraitsGranted),
|
||||
}
|
||||
|
||||
if err := g.usageEvents.SubmitUsageEvent(ctx, &proto.SubmitUsageEventRequest{
|
||||
|
||||
@@ -57,6 +57,7 @@ func TestAccessLists(t *testing.T) {
|
||||
})
|
||||
|
||||
user, err := types.NewUser("user")
|
||||
require.NoError(t, err)
|
||||
user.SetStaticLabels(map[string]string{
|
||||
"label1": "value1",
|
||||
"label2": "value2",
|
||||
@@ -65,24 +66,25 @@ func TestAccessLists(t *testing.T) {
|
||||
user.SetTraits(map[string][]string{
|
||||
"otrait1": {"value1", "value2"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userNoRolesOrTraits, err := types.NewUser("user")
|
||||
require.NoError(t, err)
|
||||
clock := clockwork.NewFakeClock()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
user types.User
|
||||
cloud bool
|
||||
accessLists []*accesslist.AccessList
|
||||
members []*accesslist.AccessListMember
|
||||
locks []types.Lock
|
||||
roles []string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
expected *userloginstate.UserLoginState
|
||||
expectedRoleCount int
|
||||
expectedTraitCount int
|
||||
name string
|
||||
user types.User
|
||||
cloud bool
|
||||
accessLists []*accesslist.AccessList
|
||||
members []*accesslist.AccessListMember
|
||||
locks []types.Lock
|
||||
roles []string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
expected *userloginstate.UserLoginState
|
||||
expectedRoleCount int
|
||||
expectedTraitCount int
|
||||
expectedInheritedRoleCount int
|
||||
expectedInheritedTraitCount int
|
||||
}{
|
||||
{
|
||||
name: "access lists are empty",
|
||||
@@ -98,9 +100,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}}),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 0,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 0,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add roles and traits",
|
||||
@@ -126,9 +131,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1", "role1", "role2"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"value1", "value2"}, "trait2": {"value3"}}),
|
||||
expectedRoleCount: 2,
|
||||
expectedTraitCount: 3,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"value1", "value2"}, "trait2": {"value3"}},
|
||||
),
|
||||
expectedRoleCount: 2,
|
||||
expectedTraitCount: 3,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "lock prevents adding roles and traits",
|
||||
@@ -157,9 +165,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": []string{"value1", "value2"}}),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 0,
|
||||
trait.Traits{"otrait1": []string{"value1", "value2"}},
|
||||
),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 0,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add member roles and traits (cloud disabled)",
|
||||
@@ -185,9 +196,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1", "role1", "role2"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"value1", "value2"}, "trait2": {"value3"}}),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 0,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"value1", "value2"}, "trait2": {"value3"}},
|
||||
),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 0,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add owner roles and traits",
|
||||
@@ -212,9 +226,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1", "owner-role1", "owner-role2"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "owner-trait1": {"owner-value1"}}),
|
||||
expectedRoleCount: 2,
|
||||
expectedTraitCount: 1,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "owner-trait1": {"owner-value1"}},
|
||||
),
|
||||
expectedRoleCount: 2,
|
||||
expectedTraitCount: 1,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add owner and member roles and traits",
|
||||
@@ -239,9 +256,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1", "owner-role1", "owner-role2", "role1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"owner-value1", "value1"}}),
|
||||
expectedRoleCount: 3,
|
||||
expectedTraitCount: 2,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"owner-value1", "value1"}},
|
||||
),
|
||||
expectedRoleCount: 3,
|
||||
expectedTraitCount: 2,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add member roles and traits, roles missing from backend",
|
||||
@@ -287,9 +307,13 @@ func TestAccessLists(t *testing.T) {
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1", "role1"},
|
||||
trait.Traits{
|
||||
"otrait1": {"value1", "value2"}, "trait1": {"value1"}}),
|
||||
expectedRoleCount: 1,
|
||||
expectedTraitCount: 1,
|
||||
"otrait1": {"value1", "value2"}, "trait1": {"value1"},
|
||||
},
|
||||
),
|
||||
expectedRoleCount: 1,
|
||||
expectedTraitCount: 1,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add roles with duplicates",
|
||||
@@ -310,9 +334,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1", "role1", "role2", "role3"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}}),
|
||||
expectedRoleCount: 3,
|
||||
expectedTraitCount: 0,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
),
|
||||
expectedRoleCount: 3,
|
||||
expectedTraitCount: 0,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add traits with duplicates",
|
||||
@@ -343,9 +370,12 @@ func TestAccessLists(t *testing.T) {
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}},
|
||||
[]string{"orole1"},
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"value1", "value2"}, "trait2": {"value3", "value4", "value1"}, "trait3": {"value5", "value6"}}),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 7,
|
||||
trait.Traits{"otrait1": {"value1", "value2"}, "trait1": {"value1", "value2"}, "trait2": {"value3", "value4", "value1"}, "trait3": {"value5", "value6"}},
|
||||
),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 7,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists add traits with no roles or traits in original",
|
||||
@@ -375,9 +405,148 @@ func TestAccessLists(t *testing.T) {
|
||||
trait.Traits{
|
||||
"trait1": {"value1", "value2"},
|
||||
"trait2": {"value3", "value4"},
|
||||
"trait3": {"value5", "value6"}}),
|
||||
expectedRoleCount: 1,
|
||||
expectedTraitCount: 6,
|
||||
"trait3": {"value5", "value6"},
|
||||
},
|
||||
),
|
||||
expectedRoleCount: 1,
|
||||
expectedTraitCount: 6,
|
||||
expectedInheritedRoleCount: 0,
|
||||
expectedInheritedTraitCount: 0,
|
||||
},
|
||||
{
|
||||
name: "access lists member of nested list",
|
||||
cloud: true,
|
||||
user: userNoRolesOrTraits,
|
||||
// user is member of acl 3, acl 1 includes acl 2, which includes 3
|
||||
// so user will be granted role1 and 2, and trait1
|
||||
accessLists: []*accesslist.AccessList{
|
||||
newAccessList(t, clock, "1", grants([]string{"role1"},
|
||||
trait.Traits{
|
||||
"trait1": {"value"},
|
||||
}),
|
||||
emptyGrants),
|
||||
newAccessList(t, clock, "2", grants([]string{"role1"}, trait.Traits{}),
|
||||
emptyGrants),
|
||||
newAccessList(t, clock, "3", grants([]string{"role2"}, trait.Traits{}), emptyGrants),
|
||||
},
|
||||
members: append(
|
||||
newAccessListMembers(t, clock, "3", "user"),
|
||||
newAccessListMemberWithKind(t, clock, "2", accesslist.MembershipKindList, "3"),
|
||||
newAccessListMemberWithKind(t, clock, "1", accesslist.MembershipKindList, "2")),
|
||||
roles: []string{"role1", "role2"},
|
||||
wantErr: require.NoError,
|
||||
expected: newUserLoginState(t, "user",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
[]string{"role1", "role2"},
|
||||
trait.Traits{"trait1": {"value"}},
|
||||
),
|
||||
expectedRoleCount: 2,
|
||||
expectedTraitCount: 1,
|
||||
expectedInheritedRoleCount: 1,
|
||||
expectedInheritedTraitCount: 1,
|
||||
},
|
||||
{
|
||||
name: "access lists member of nested list",
|
||||
cloud: true,
|
||||
user: userNoRolesOrTraits,
|
||||
// user is member of acl 3, acl 1 includes acl 2, which includes 3
|
||||
// so user will be granted role1 and 2, and trait1
|
||||
accessLists: []*accesslist.AccessList{
|
||||
newAccessList(t, clock, "1", grants([]string{"role1"},
|
||||
trait.Traits{
|
||||
"trait1": {"value"},
|
||||
}),
|
||||
emptyGrants),
|
||||
newAccessList(t, clock, "2", grants([]string{"role1"}, trait.Traits{}),
|
||||
emptyGrants),
|
||||
newAccessList(t, clock, "3", grants([]string{"role2"}, trait.Traits{}), emptyGrants),
|
||||
},
|
||||
members: append(
|
||||
newAccessListMembers(t, clock, "3", "user"),
|
||||
newAccessListMemberWithKind(t, clock, "2", accesslist.MembershipKindList, "3"),
|
||||
newAccessListMemberWithKind(t, clock, "1", accesslist.MembershipKindList, "2")),
|
||||
roles: []string{"role1", "role2"},
|
||||
wantErr: require.NoError,
|
||||
expected: newUserLoginState(t, "user",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
[]string{"role1", "role2"},
|
||||
trait.Traits{"trait1": {"value"}},
|
||||
),
|
||||
expectedRoleCount: 2,
|
||||
expectedTraitCount: 1,
|
||||
expectedInheritedRoleCount: 1,
|
||||
expectedInheritedTraitCount: 1,
|
||||
},
|
||||
{
|
||||
name: "access lists member of nested list, in diamond formation",
|
||||
cloud: true,
|
||||
user: userNoRolesOrTraits,
|
||||
// user is member of acl 1, acl 2 and 3 include acl 1, acl 4 includes acls 2 and 3
|
||||
// so user will be granted {trait: [1,2,3,4]}
|
||||
accessLists: []*accesslist.AccessList{
|
||||
newAccessList(t, clock, "1", grants([]string{}, trait.Traits{"trait": {"1"}}), emptyGrants),
|
||||
newAccessList(t, clock, "2", grants([]string{}, trait.Traits{"trait": {"2"}}), emptyGrants),
|
||||
newAccessList(t, clock, "3", grants([]string{}, trait.Traits{"trait": {"3"}}), emptyGrants),
|
||||
newAccessList(t, clock, "4", grants([]string{}, trait.Traits{"trait": {"4"}}), emptyGrants),
|
||||
},
|
||||
members: append(
|
||||
newAccessListMembers(t, clock, "1", "user"),
|
||||
newAccessListMemberWithKind(t, clock, "2", accesslist.MembershipKindList, "1"),
|
||||
newAccessListMemberWithKind(t, clock, "3", accesslist.MembershipKindList, "1"),
|
||||
newAccessListMemberWithKind(t, clock, "4", accesslist.MembershipKindList, "3"),
|
||||
newAccessListMemberWithKind(t, clock, "4", accesslist.MembershipKindList, "2"),
|
||||
),
|
||||
roles: nil,
|
||||
wantErr: require.NoError,
|
||||
expected: newUserLoginState(t, "user",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
trait.Traits{"trait": {"1", "2", "3", "4"}},
|
||||
),
|
||||
expectedRoleCount: 0,
|
||||
expectedTraitCount: 4,
|
||||
expectedInheritedRoleCount: 0,
|
||||
// trait 1 is directly granted to user via acl 1; it is not inherited
|
||||
expectedInheritedTraitCount: 3,
|
||||
},
|
||||
{
|
||||
name: "members in nested access lists inherit parent's owner grants",
|
||||
cloud: true,
|
||||
user: userNoRolesOrTraits,
|
||||
// user is member of acl 1, acl 3, includes members of acl 2, which includes members of acl 1 as owners
|
||||
accessLists: []*accesslist.AccessList{
|
||||
newAccessList(t, clock, "1", emptyGrants, grants([]string{"oroleA"}, trait.Traits{"okey": {"oval1"}})),
|
||||
newAccessList(t, clock, "2", emptyGrants, grants([]string{"oroleB"}, trait.Traits{"okey": {"oval2"}})),
|
||||
newAccessListWithOwners(t, clock, "3", emptyGrants, grants([]string{"oroleC"}, trait.Traits{"okey": {"oval3"}}), []accesslist.Owner{{
|
||||
Name: "2",
|
||||
Description: "hello",
|
||||
MembershipKind: accesslist.MembershipKindList},
|
||||
}),
|
||||
},
|
||||
members: append(
|
||||
newAccessListMembers(t, clock, "1", "user"),
|
||||
newAccessListMemberWithKind(t, clock, "2", accesslist.MembershipKindList, "1"),
|
||||
newAccessListMemberWithKind(t, clock, "3", accesslist.MembershipKindList, "2"),
|
||||
),
|
||||
roles: []string{"oroleA", "oroleB", "oroleC"},
|
||||
wantErr: require.NoError,
|
||||
expected: newUserLoginState(t, "user",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
[]string{"oroleC"},
|
||||
trait.Traits{"okey": {"oval3"}},
|
||||
),
|
||||
expectedRoleCount: 1,
|
||||
expectedTraitCount: 1,
|
||||
expectedInheritedRoleCount: 1,
|
||||
expectedInheritedTraitCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -438,6 +607,8 @@ func TestAccessLists(t *testing.T) {
|
||||
|
||||
require.Equal(t, test.expectedRoleCount, int(event.AccessListGrantsToUser.CountRolesGranted))
|
||||
require.Equal(t, test.expectedTraitCount, int(event.AccessListGrantsToUser.CountTraitsGranted))
|
||||
require.Equal(t, test.expectedInheritedRoleCount, int(event.AccessListGrantsToUser.CountInheritedRolesGranted))
|
||||
require.Equal(t, test.expectedInheritedTraitCount, int(event.AccessListGrantsToUser.CountInheritedTraitsGranted))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -521,6 +692,33 @@ func newAccessList(t *testing.T, clock clockwork.Clock, name string, grants acce
|
||||
return accessList
|
||||
}
|
||||
|
||||
func newAccessListWithOwners(t *testing.T, clock clockwork.Clock, name string, grants accesslist.Grants, ownerGrants accesslist.Grants, owners []accesslist.Owner) *accesslist.AccessList {
|
||||
t.Helper()
|
||||
|
||||
accessList, err := accesslist.NewAccessList(header.Metadata{
|
||||
Name: name,
|
||||
}, accesslist.Spec{
|
||||
Title: "title",
|
||||
Audit: accesslist.Audit{
|
||||
NextAuditDate: clock.Now().Add(time.Hour * 48),
|
||||
},
|
||||
Owners: owners,
|
||||
OwnershipRequires: accesslist.Requires{
|
||||
Roles: []string{},
|
||||
Traits: map[string][]string{},
|
||||
},
|
||||
MembershipRequires: accesslist.Requires{
|
||||
Roles: []string{},
|
||||
Traits: map[string][]string{},
|
||||
},
|
||||
Grants: grants,
|
||||
OwnerGrants: ownerGrants,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return accessList
|
||||
}
|
||||
|
||||
func newAccessListMembers(t *testing.T, clock clockwork.Clock, accessList string, members ...string) []*accesslist.AccessListMember {
|
||||
alMembers := make([]*accesslist.AccessListMember, len(members))
|
||||
for i, member := range members {
|
||||
@@ -541,6 +739,25 @@ func newAccessListMembers(t *testing.T, clock clockwork.Clock, accessList string
|
||||
return alMembers
|
||||
}
|
||||
|
||||
func newAccessListMemberWithKind(t *testing.T, clock clockwork.Clock, accessList string, kind string, member string) *accesslist.AccessListMember {
|
||||
|
||||
var err error
|
||||
res, err := accesslist.NewAccessListMember(header.Metadata{
|
||||
Name: member,
|
||||
}, accesslist.AccessListMemberSpec{
|
||||
AccessList: accessList,
|
||||
Name: member,
|
||||
Joined: clock.Now(),
|
||||
Expires: clock.Now().Add(24 * time.Hour),
|
||||
Reason: "added",
|
||||
AddedBy: ownerUser,
|
||||
MembershipKind: kind,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func newUserLoginState(t *testing.T, name string, labels map[string]string, originalRoles []string, originalTraits map[string][]string,
|
||||
roles []string, traits map[string][]string) *userloginstate.UserLoginState {
|
||||
t.Helper()
|
||||
|
||||
@@ -1232,7 +1232,8 @@ func definitionForBuiltinRole(clusterName string, recConfig readonly.SessionReco
|
||||
types.NewRule(types.KindRole, services.RO()),
|
||||
types.NewRule(types.KindLock, services.RW()),
|
||||
types.NewRule(types.KindSAML, services.ReadNoSecrets()),
|
||||
// Okta can manage access lists and roles it creates.
|
||||
types.NewRule(types.KindAccessList, services.RO()),
|
||||
// Okta can read/write access lists and roles it creates.
|
||||
{
|
||||
Resources: []string{types.KindRole},
|
||||
Verbs: services.RW(),
|
||||
|
||||
Vendored
+2
-2
@@ -3327,13 +3327,13 @@ func (c *Cache) GetAccessList(ctx context.Context, name string) (*accesslist.Acc
|
||||
}
|
||||
|
||||
// CountAccessListMembers will count all access list members.
|
||||
func (c *Cache) CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error) {
|
||||
func (c *Cache) CountAccessListMembers(ctx context.Context, accessListName string) (uint32, uint32, error) {
|
||||
ctx, span := c.Tracer.Start(ctx, "cache/CountAccessListMembers")
|
||||
defer span.End()
|
||||
|
||||
rg, err := readCollectionCache(c, c.collections.accessListMembers)
|
||||
if err != nil {
|
||||
return 0, trace.Wrap(err)
|
||||
return 0, 0, trace.Wrap(err)
|
||||
}
|
||||
defer rg.Release()
|
||||
return rg.reader.CountAccessListMembers(ctx, accessListName)
|
||||
|
||||
Vendored
+4
-3
@@ -2586,16 +2586,17 @@ func TestAccessListMembers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
count, err := p.accessLists.CountAccessListMembers(ctx, al.GetName())
|
||||
count, listCount, err := p.accessLists.CountAccessListMembers(ctx, al.GetName())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint32(40), count)
|
||||
require.Equal(t, uint32(0), listCount)
|
||||
|
||||
// Eventually, this should be reflected in the cache.
|
||||
require.Eventually(t, func() bool {
|
||||
// Make sure the cache has a single resource in it.
|
||||
count, err := p.cache.CountAccessListMembers(ctx, al.GetName())
|
||||
count, listCount, err := p.cache.CountAccessListMembers(ctx, al.GetName())
|
||||
assert.NoError(t, err)
|
||||
return count == uint32(40)
|
||||
return count == uint32(40) && listCount == uint32(0)
|
||||
}, time.Second*2, time.Millisecond*250)
|
||||
}
|
||||
|
||||
|
||||
Vendored
+5
-5
@@ -3175,7 +3175,7 @@ func (accessListExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets
|
||||
}
|
||||
|
||||
func (accessListExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.AccessList) error {
|
||||
_, err := cache.accessListCache.UpsertAccessList(ctx, resource)
|
||||
_, err := cache.accessListCache.UnconditionalUpsertAccessList(ctx, resource)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -3184,7 +3184,7 @@ func (accessListExecutor) deleteAll(ctx context.Context, cache *Cache) error {
|
||||
}
|
||||
|
||||
func (accessListExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error {
|
||||
return cache.accessListCache.DeleteAccessList(ctx, resource.GetName())
|
||||
return cache.accessListCache.UnconditionalDeleteAccessList(ctx, resource.GetName())
|
||||
}
|
||||
|
||||
func (accessListExecutor) isSingleton() bool { return false }
|
||||
@@ -3225,7 +3225,7 @@ func (accessListMemberExecutor) getAll(ctx context.Context, cache *Cache, loadSe
|
||||
}
|
||||
|
||||
func (accessListMemberExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.AccessListMember) error {
|
||||
_, err := cache.accessListCache.UpsertAccessListMember(ctx, resource)
|
||||
_, err := cache.accessListCache.UnconditionalUpsertAccessListMember(ctx, resource)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -3234,7 +3234,7 @@ func (accessListMemberExecutor) deleteAll(ctx context.Context, cache *Cache) err
|
||||
}
|
||||
|
||||
func (accessListMemberExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error {
|
||||
return cache.accessListCache.DeleteAccessListMember(ctx,
|
||||
return cache.accessListCache.UnconditionalDeleteAccessListMember(ctx,
|
||||
resource.GetMetadata().Description, // Cache passes access ID via description field.
|
||||
resource.GetName())
|
||||
}
|
||||
@@ -3249,7 +3249,7 @@ func (accessListMemberExecutor) getReader(cache *Cache, cacheOK bool) accessList
|
||||
}
|
||||
|
||||
type accessListMembersGetter interface {
|
||||
CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error)
|
||||
CountAccessListMembers(ctx context.Context, accessListName string) (uint32, uint32, error)
|
||||
ListAccessListMembers(ctx context.Context, accessListName string, pageSize int, nextToken string) ([]*accesslist.AccessListMember, string, error)
|
||||
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
|
||||
ListAllAccessListMembers(ctx context.Context, pageSize int, pageToken string) ([]*accesslist.AccessListMember, string, error)
|
||||
|
||||
+10
-3
@@ -236,6 +236,9 @@ type AccessResourcesGetter interface {
|
||||
ListAccessLists(context.Context, int, string) ([]*accesslist.AccessList, string, error)
|
||||
ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error)
|
||||
|
||||
GetAccessList(context.Context, string) (*accesslist.AccessList, error)
|
||||
GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, error)
|
||||
|
||||
ListAccessListMembers(ctx context.Context, accessList string, pageSize int, pageToken string) (members []*accesslist.AccessListMember, nextToken string, err error)
|
||||
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
|
||||
|
||||
@@ -257,8 +260,12 @@ type AccessListSuggestionClient interface {
|
||||
type RoleGetter interface {
|
||||
GetRole(ctx context.Context, name string) (types.Role, error)
|
||||
}
|
||||
type AccessListGetter interface {
|
||||
|
||||
type AccessListAndMembersGetter interface {
|
||||
GetAccessList(ctx context.Context, name string) (*accesslist.AccessList, error)
|
||||
GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, error)
|
||||
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
|
||||
ListAccessListMembers(ctx context.Context, accessListName string, pageSize int, pageToken string) (members []*accesslist.AccessListMember, nextToken string, err error)
|
||||
}
|
||||
|
||||
// Modules defines interface that external libraries can implement customizing
|
||||
@@ -283,7 +290,7 @@ type Modules interface {
|
||||
// GenerateAccessRequestPromotions generates a list of valid promotions for given access request.
|
||||
GenerateAccessRequestPromotions(context.Context, AccessResourcesGetter, types.AccessRequest) (*types.AccessRequestAllowedPromotions, error)
|
||||
// GetSuggestedAccessLists generates a list of valid promotions for given access request.
|
||||
GetSuggestedAccessLists(ctx context.Context, identity *tlsca.Identity, clt AccessListSuggestionClient, accessListGetter AccessListGetter, requestID string) ([]*accesslist.AccessList, error)
|
||||
GetSuggestedAccessLists(ctx context.Context, identity *tlsca.Identity, clt AccessListSuggestionClient, accessListGetter AccessListAndMembersGetter, requestID string) ([]*accesslist.AccessList, error)
|
||||
// EnableRecoveryCodes enables the usage of recovery codes for resetting forgotten passwords
|
||||
EnableRecoveryCodes()
|
||||
// EnablePlugins enables the hosted plugins runtime
|
||||
@@ -427,7 +434,7 @@ func (p *defaultModules) GenerateAccessRequestPromotions(_ context.Context, _ Ac
|
||||
}
|
||||
|
||||
func (p *defaultModules) GetSuggestedAccessLists(ctx context.Context, identity *tlsca.Identity, clt AccessListSuggestionClient,
|
||||
accessListGetter AccessListGetter, requestID string,
|
||||
accessListGetter AccessListAndMembersGetter, requestID string,
|
||||
) ([]*accesslist.AccessList, error) {
|
||||
return nil, trace.NotImplemented("GetSuggestedAccessLists not implemented")
|
||||
}
|
||||
|
||||
+9
-138
@@ -20,17 +20,13 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
|
||||
accesslistclient "github.com/gravitational/teleport/api/client/accesslist"
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
@@ -48,6 +44,8 @@ type AccessListsGetter interface {
|
||||
GetAccessList(context.Context, string) (*accesslist.AccessList, error)
|
||||
// GetAccessListsToReview returns access lists that the user needs to review.
|
||||
GetAccessListsToReview(context.Context) ([]*accesslist.AccessList, error)
|
||||
// GetInheritedGrants returns grants inherited by access list accessListID from parent access lists.
|
||||
GetInheritedGrants(context.Context, string) (*accesslist.Grants, error)
|
||||
}
|
||||
|
||||
// AccessListsSuggestionsGetter defines an interface for reading access lists suggestions.
|
||||
@@ -140,6 +138,10 @@ type AccessListMemberGetter interface {
|
||||
// implicit member list and the underlying implementation does not have
|
||||
// enough information to compute the dynamic member record.
|
||||
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
|
||||
// GetAccessList returns the specified access list resource.
|
||||
GetAccessList(context.Context, string) (*accesslist.AccessList, error)
|
||||
// GetAccessLists returns a list of all access lists.
|
||||
GetAccessLists(context.Context) ([]*accesslist.AccessList, error)
|
||||
}
|
||||
|
||||
// AccessListMembersGetter defines an interface for reading access list members.
|
||||
@@ -147,7 +149,7 @@ type AccessListMembersGetter interface {
|
||||
AccessListMemberGetter
|
||||
|
||||
// CountAccessListMembers will count all access list members.
|
||||
CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error)
|
||||
CountAccessListMembers(ctx context.Context, accessListName string) (membersCount uint32, listCount uint32, err error)
|
||||
// ListAccessListMembers returns a paginated list of all access list members.
|
||||
// May return a DynamicAccessListError if the requested access list has an
|
||||
// implicit member list and the underlying implementation does not have
|
||||
@@ -156,6 +158,8 @@ type AccessListMembersGetter interface {
|
||||
// ListAllAccessListMembers returns a paginated list of all access list members for all access lists.
|
||||
ListAllAccessListMembers(ctx context.Context, pageSize int, pageToken string) (members []*accesslist.AccessListMember, nextToken string, err error)
|
||||
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
|
||||
// GetAccessListOwners returns a list of all owners in an Access List, including those inherited from nested Access Lists.
|
||||
GetAccessListOwners(ctx context.Context, accessList string) ([]*accesslist.Owner, error)
|
||||
}
|
||||
|
||||
// AccessListMembers defines an interface for managing AccessListMembers.
|
||||
@@ -218,139 +222,6 @@ func UnmarshalAccessListMember(data []byte, opts ...MarshalOption) (*accesslist.
|
||||
return &member, nil
|
||||
}
|
||||
|
||||
// IsAccessListOwner will return true if the user is an owner for the current list.
|
||||
func IsAccessListOwner(identity tlsca.Identity, accessList *accesslist.AccessList) error {
|
||||
// An opaque access denied error.
|
||||
accessDenied := trace.AccessDenied("access denied")
|
||||
|
||||
// Is the supplied identity in the owners list?
|
||||
ownerIdx := slices.IndexFunc(accessList.GetOwners(), func(owner accesslist.Owner) bool {
|
||||
return owner.Name == identity.Username
|
||||
})
|
||||
if ownerIdx == -1 {
|
||||
return accessDenied
|
||||
}
|
||||
|
||||
// Does the supplied Identity meet the ownership requirements?
|
||||
if !UserMeetsRequirements(identity, accessList.Spec.OwnershipRequires) {
|
||||
return accessDenied
|
||||
}
|
||||
|
||||
// We've gotten through all the checks, so the user is an owner.
|
||||
return nil
|
||||
}
|
||||
|
||||
// AccessListMembershipChecker will check if users are members of an access list and
|
||||
// makes sure the user is not locked and meets membership requirements.
|
||||
type AccessListMembershipChecker struct {
|
||||
members AccessListMemberGetter
|
||||
locks LockGetter
|
||||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
// NewAccessListMembershipChecker will create a new access list membership checker.
|
||||
func NewAccessListMembershipChecker(clock clockwork.Clock, members AccessListMemberGetter, locks LockGetter) *AccessListMembershipChecker {
|
||||
return &AccessListMembershipChecker{
|
||||
members: members,
|
||||
locks: locks,
|
||||
clock: clock,
|
||||
}
|
||||
}
|
||||
|
||||
// IsAccessListMember will return true if the user is a member for the current list.
|
||||
func (a AccessListMembershipChecker) IsAccessListMember(ctx context.Context, identity tlsca.Identity, accessList *accesslist.AccessList) error {
|
||||
username := identity.Username
|
||||
|
||||
// Allow for nil locks while we transition away from using `IsAccessListMember` outside of this struct.
|
||||
if a.locks != nil {
|
||||
locks, err := a.locks.GetLocks(ctx, true, types.LockTarget{
|
||||
User: username,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if len(locks) > 0 {
|
||||
return trace.AccessDenied("user %s is currently locked", username)
|
||||
}
|
||||
}
|
||||
|
||||
member, err := a.members.GetAccessListMember(ctx, accessList.GetName(), username)
|
||||
if trace.IsNotFound(err) {
|
||||
// The member has not been found, so we know they're not a member of this list.
|
||||
return trace.NotFound("user %s is not a member of the access list", username)
|
||||
} else if err != nil {
|
||||
// Some other error has occurred
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
expires := member.Spec.Expires
|
||||
if !expires.IsZero() && !a.clock.Now().Before(expires) {
|
||||
return trace.AccessDenied("user %s's membership has expired in the access list", username)
|
||||
}
|
||||
|
||||
if !UserMeetsRequirements(identity, accessList.Spec.MembershipRequires) {
|
||||
return trace.AccessDenied("user %s is a member, but does not have the roles or traits required to be a member of this list", username)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(mdwn): Remove this in favor of using the access list membership checker.
|
||||
func IsAccessListMember(ctx context.Context, identity tlsca.Identity, clock clockwork.Clock, accessList *accesslist.AccessList, members AccessListMemberGetter) error {
|
||||
// See if the member getter also implements lock getter. If so, use it. Otherwise, nil is fine.
|
||||
lockGetter, _ := members.(LockGetter)
|
||||
return AccessListMembershipChecker{
|
||||
members: members,
|
||||
locks: lockGetter,
|
||||
clock: clock,
|
||||
}.IsAccessListMember(ctx, identity, accessList)
|
||||
}
|
||||
|
||||
// UserMeetsRequirements will return true if the user meets the requirements for the access list.
|
||||
func UserMeetsRequirements(identity tlsca.Identity, requires accesslist.Requires) bool {
|
||||
// Assemble the user's roles for easy look up.
|
||||
userRolesMap := map[string]struct{}{}
|
||||
for _, role := range identity.Groups {
|
||||
userRolesMap[role] = struct{}{}
|
||||
}
|
||||
|
||||
// Check that the user meets the role requirements.
|
||||
for _, role := range requires.Roles {
|
||||
if _, ok := userRolesMap[role]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble traits for easy lookup.
|
||||
userTraitsMap := map[string]map[string]struct{}{}
|
||||
for k, values := range identity.Traits {
|
||||
if _, ok := userTraitsMap[k]; !ok {
|
||||
userTraitsMap[k] = map[string]struct{}{}
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
userTraitsMap[k][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that user meets trait requirements.
|
||||
for k, values := range requires.Traits {
|
||||
if _, ok := userTraitsMap[k]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
if _, ok := userTraitsMap[k][v]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The user meets all requirements.
|
||||
return true
|
||||
}
|
||||
|
||||
// AccessListReviews defines an interface for managing Access List reviews.
|
||||
type AccessListReviews interface {
|
||||
// ListAccessListReviews will list access list reviews for a particular access list.
|
||||
|
||||
@@ -19,30 +19,18 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
"github.com/gravitational/teleport/api/types/header"
|
||||
"github.com/gravitational/teleport/api/types/trait"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
ownerUser = "owner-user"
|
||||
member1 = "member1"
|
||||
member2 = "member2"
|
||||
member3 = "member3"
|
||||
member4 = "member4"
|
||||
)
|
||||
|
||||
// TestAccessListUnmarshal verifies an access list resource can be unmarshaled.
|
||||
func TestAccessListUnmarshal(t *testing.T) {
|
||||
expected, err := accesslist.NewAccessList(
|
||||
@@ -197,304 +185,11 @@ func TestAccessListMemberMarshal(t *testing.T) {
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestIsAccessListOwner(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
identity tlsca.Identity
|
||||
errAssertionFunc require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "is owner",
|
||||
identity: tlsca.Identity{
|
||||
Username: ownerUser,
|
||||
Groups: []string{"orole1", "orole2"},
|
||||
Traits: map[string][]string{
|
||||
"otrait1": {"ovalue1", "ovalue2"},
|
||||
"otrait2": {"ovalue3", "ovalue4"},
|
||||
},
|
||||
},
|
||||
errAssertionFunc: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "is not an owner",
|
||||
identity: tlsca.Identity{
|
||||
Username: "not-owner",
|
||||
Groups: []string{"orole1", "orole2"},
|
||||
Traits: map[string][]string{
|
||||
"otrait1": {"ovalue1", "ovalue2"},
|
||||
"otrait2": {"ovalue3", "ovalue4"},
|
||||
},
|
||||
},
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
{
|
||||
name: "is owner with missing roles",
|
||||
identity: tlsca.Identity{
|
||||
Username: "not-owner",
|
||||
Groups: []string{"orole1"},
|
||||
Traits: map[string][]string{
|
||||
"otrait1": {"ovalue1", "ovalue2"},
|
||||
"otrait2": {"ovalue3", "ovalue4"},
|
||||
},
|
||||
},
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
{
|
||||
name: "is owner with missing traits",
|
||||
identity: tlsca.Identity{
|
||||
Username: "not-owner",
|
||||
Groups: []string{"orole1", "orole2"},
|
||||
Traits: map[string][]string{
|
||||
"otrait1": {"ovalue1"},
|
||||
"otrait2": {"ovalue3"},
|
||||
},
|
||||
},
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessList := newAccessList(t)
|
||||
|
||||
test.errAssertionFunc(t, IsAccessListOwner(test.identity, accessList))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testMembersAndLockGetter implements AccessListMembersGetter and LockGetter for testing.
|
||||
type testMembersAndLockGetter struct {
|
||||
members map[string]map[string]*accesslist.AccessListMember
|
||||
locks map[string]types.Lock
|
||||
}
|
||||
|
||||
// ListAccessListMembers returns a paginated list of all access list members.
|
||||
func (t *testMembersAndLockGetter) ListAccessListMembers(ctx context.Context, accessList string, _ int, _ string) (members []*accesslist.AccessListMember, nextToken string, err error) {
|
||||
for _, member := range t.members[accessList] {
|
||||
members = append(members, member)
|
||||
}
|
||||
return members, "", nil
|
||||
}
|
||||
|
||||
// ListAllAccessListMembers returns a paginated list of all access list members for all access lists.
|
||||
func (t *testMembersAndLockGetter) ListAllAccessListMembers(ctx context.Context, pageSize int, pageToken string) ([]*accesslist.AccessListMember, string, error) {
|
||||
var allMembers []*accesslist.AccessListMember
|
||||
for _, members := range t.members {
|
||||
for _, member := range members {
|
||||
allMembers = append(allMembers, member)
|
||||
}
|
||||
}
|
||||
|
||||
return allMembers, "", nil
|
||||
}
|
||||
|
||||
// GetAccessListMember returns the specified access list member resource.
|
||||
func (t *testMembersAndLockGetter) GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error) {
|
||||
members, ok := t.members[accessList]
|
||||
if !ok {
|
||||
return nil, trace.NotFound("not found")
|
||||
}
|
||||
|
||||
member, ok := members[memberName]
|
||||
if !ok {
|
||||
return nil, trace.NotFound("not found")
|
||||
}
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
// GetLock gets a lock by name.
|
||||
func (t *testMembersAndLockGetter) GetLock(_ context.Context, name string) (types.Lock, error) {
|
||||
if t.locks == nil {
|
||||
return nil, trace.NotFound("not found")
|
||||
}
|
||||
|
||||
lock, ok := t.locks[name]
|
||||
if !ok {
|
||||
return nil, trace.NotFound("not found")
|
||||
}
|
||||
|
||||
return lock, nil
|
||||
}
|
||||
|
||||
// GetLocks gets all/in-force locks that match at least one of the targets when specified.
|
||||
func (t *testMembersAndLockGetter) GetLocks(ctx context.Context, inForceOnly bool, targets ...types.LockTarget) ([]types.Lock, error) {
|
||||
locks := make([]types.Lock, 0, len(t.locks))
|
||||
for _, lock := range t.locks {
|
||||
locks = append(locks, lock)
|
||||
}
|
||||
return locks, nil
|
||||
}
|
||||
|
||||
func requireAccessDenied(t require.TestingT, err error, i ...interface{}) {
|
||||
require.Error(t, err)
|
||||
require.True(t, trace.IsAccessDenied(err), "expected AccessDenied, got %T: %s", err, err.Error())
|
||||
}
|
||||
|
||||
func TestIsAccessListMemberChecker(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
identity tlsca.Identity
|
||||
memberCtx context.Context
|
||||
currentTime time.Time
|
||||
locks map[string]types.Lock
|
||||
errAssertionFunc require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "is member",
|
||||
identity: tlsca.Identity{
|
||||
Username: member1,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "is locked member",
|
||||
identity: tlsca.Identity{
|
||||
Username: member1,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
locks: map[string]types.Lock{
|
||||
"test-lock": newUserLock(t, "test-lock", member1),
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: func(t require.TestingT, err error, i ...interface{}) {
|
||||
require.ErrorIs(t, err, trace.AccessDenied("user %s is currently locked", member1))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "is not a member",
|
||||
identity: tlsca.Identity{
|
||||
Username: member4,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: func(t require.TestingT, err error, i ...interface{}) {
|
||||
require.True(t, trace.IsNotFound(err))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "is expired member",
|
||||
identity: tlsca.Identity{
|
||||
Username: member2,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
{
|
||||
name: "member has no expiration",
|
||||
identity: tlsca.Identity{
|
||||
Username: member3,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2030, 7, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "is member with missing roles",
|
||||
identity: tlsca.Identity{
|
||||
Username: member1,
|
||||
Groups: []string{"mrole1"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
{
|
||||
name: "is member with no expiration and missing roles",
|
||||
identity: tlsca.Identity{
|
||||
Username: member3,
|
||||
Groups: []string{"mrole1"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
{
|
||||
name: "is member with missing traits",
|
||||
identity: tlsca.Identity{
|
||||
Username: member1,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1"},
|
||||
"mtrait2": {"mvalue3"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
{
|
||||
name: "is member with no expiration and missing traits",
|
||||
identity: tlsca.Identity{
|
||||
Username: member3,
|
||||
Groups: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1"},
|
||||
"mtrait2": {"mvalue3"},
|
||||
},
|
||||
},
|
||||
currentTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
errAssertionFunc: requireAccessDenied,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
accessList := newAccessList(t)
|
||||
members := newAccessListMembers(t)
|
||||
|
||||
memberMap := map[string]map[string]*accesslist.AccessListMember{}
|
||||
for _, member := range members {
|
||||
accessListName := member.Spec.AccessList
|
||||
if _, ok := memberMap[accessListName]; !ok {
|
||||
memberMap[accessListName] = map[string]*accesslist.AccessListMember{}
|
||||
}
|
||||
memberMap[accessListName][member.Spec.Name] = member
|
||||
}
|
||||
getter := &testMembersAndLockGetter{members: memberMap, locks: test.locks}
|
||||
|
||||
checker := NewAccessListMembershipChecker(clockwork.NewFakeClockAt(test.currentTime), getter, getter)
|
||||
test.errAssertionFunc(t, checker.IsAccessListMember(ctx, test.identity, accessList))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAccessListReviewUnmarshal verifies an access list review resource can be unmarshaled.
|
||||
func TestAccessListReviewUnmarshal(t *testing.T) {
|
||||
expected, err := accesslist.NewReview(
|
||||
@@ -596,102 +291,6 @@ func TestAccessListReviewMarshal(t *testing.T) {
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func newAccessList(t *testing.T) *accesslist.AccessList {
|
||||
t.Helper()
|
||||
|
||||
accessList, err := accesslist.NewAccessList(
|
||||
header.Metadata{
|
||||
Name: "test",
|
||||
},
|
||||
accesslist.Spec{
|
||||
Title: "title",
|
||||
Description: "test access list",
|
||||
Owners: []accesslist.Owner{
|
||||
{
|
||||
Name: ownerUser,
|
||||
Description: "owner user",
|
||||
},
|
||||
{
|
||||
Name: "test-user2",
|
||||
Description: "test user 2",
|
||||
},
|
||||
},
|
||||
Audit: accesslist.Audit{
|
||||
NextAuditDate: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
|
||||
Recurrence: accesslist.Recurrence{
|
||||
Frequency: accesslist.ThreeMonths,
|
||||
DayOfMonth: accesslist.FifteenthDayOfMonth,
|
||||
},
|
||||
},
|
||||
MembershipRequires: accesslist.Requires{
|
||||
Roles: []string{"mrole1", "mrole2"},
|
||||
Traits: map[string][]string{
|
||||
"mtrait1": {"mvalue1", "mvalue2"},
|
||||
"mtrait2": {"mvalue3", "mvalue4"},
|
||||
},
|
||||
},
|
||||
OwnershipRequires: accesslist.Requires{
|
||||
Roles: []string{"orole1", "orole2"},
|
||||
Traits: map[string][]string{
|
||||
"otrait1": {"ovalue1", "ovalue2"},
|
||||
"otrait2": {"ovalue3", "ovalue4"},
|
||||
},
|
||||
},
|
||||
Grants: accesslist.Grants{
|
||||
Roles: []string{"grole1", "grole2"},
|
||||
Traits: map[string][]string{
|
||||
"gtrait1": {"gvalue1", "gvalue2"},
|
||||
"gtrait2": {"gvalue3", "gvalue4"},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
return accessList
|
||||
}
|
||||
|
||||
func newAccessListMembers(t *testing.T) []*accesslist.AccessListMember {
|
||||
t.Helper()
|
||||
|
||||
member1, err := accesslist.NewAccessListMember(header.Metadata{
|
||||
Name: member1,
|
||||
}, accesslist.AccessListMemberSpec{
|
||||
AccessList: "test",
|
||||
Name: member1,
|
||||
Joined: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Expires: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Reason: "because",
|
||||
AddedBy: ownerUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
member2, err := accesslist.NewAccessListMember(header.Metadata{
|
||||
Name: member2,
|
||||
}, accesslist.AccessListMemberSpec{
|
||||
AccessList: "test",
|
||||
Name: member2,
|
||||
Joined: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Expires: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Reason: "because again",
|
||||
AddedBy: ownerUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
member3, err := accesslist.NewAccessListMember(header.Metadata{
|
||||
Name: member3,
|
||||
}, accesslist.AccessListMemberSpec{
|
||||
AccessList: "test",
|
||||
Name: member3,
|
||||
Joined: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Reason: "because for the third time",
|
||||
AddedBy: ownerUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return []*accesslist.AccessListMember{member1, member2, member3}
|
||||
}
|
||||
|
||||
var accessListYAML = `---
|
||||
kind: access_list
|
||||
version: v1
|
||||
@@ -787,16 +386,3 @@ spec:
|
||||
review_frequency_changed: 3 months
|
||||
review_day_of_month_changed: "15"
|
||||
`
|
||||
|
||||
func newUserLock(t *testing.T, name, user string) types.Lock {
|
||||
t.Helper()
|
||||
|
||||
lock, err := types.NewLock(name, types.LockSpecV2{
|
||||
Target: types.LockTarget{
|
||||
User: user,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return lock
|
||||
}
|
||||
|
||||
+479
-142
@@ -20,6 +20,7 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
"github.com/gravitational/teleport/api/types/header"
|
||||
"github.com/gravitational/teleport/entitlements"
|
||||
"github.com/gravitational/teleport/lib/accesslists"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
@@ -59,6 +61,11 @@ const (
|
||||
// creation or update of AccessLists in order to enforce the license limit
|
||||
// on the number AccessLists in a cluster.
|
||||
createAccessListLimitLockName = "createAccessListLimitLock"
|
||||
// accessListResourceLockName is the lock used to prevent simultaneous
|
||||
// writing to any AccessList resources (AccessLists, AccessListMembers).
|
||||
// it shares the same string as createAccessListLimitLockName to ensure
|
||||
// backwards compatibility.
|
||||
accessListResourceLockName = createAccessListLimitLockName
|
||||
)
|
||||
|
||||
// AccessListService manages Access List resources in the Backend. The AccessListService's
|
||||
@@ -74,6 +81,18 @@ type AccessListService struct {
|
||||
reviewService *generic.Service[*accesslist.Review]
|
||||
}
|
||||
|
||||
type accessListAndMembersGetter struct {
|
||||
service *generic.Service[*accesslist.AccessList]
|
||||
memberService *generic.Service[*accesslist.AccessListMember]
|
||||
}
|
||||
|
||||
func (s *accessListAndMembersGetter) ListAccessListMembers(ctx context.Context, accessListName string, pageSize int, pageToken string) ([]*accesslist.AccessListMember, string, error) {
|
||||
return s.memberService.WithPrefix(accessListName).ListResources(ctx, pageSize, pageToken)
|
||||
}
|
||||
func (s *accessListAndMembersGetter) GetAccessList(ctx context.Context, name string) (*accesslist.AccessList, error) {
|
||||
return s.service.GetResource(ctx, name)
|
||||
}
|
||||
|
||||
// compile-time assertion that the AccessListService implements the AccessLists
|
||||
// interface
|
||||
var _ services.AccessLists = (*AccessListService)(nil)
|
||||
@@ -138,6 +157,12 @@ func (a *AccessListService) GetAccessLists(ctx context.Context) ([]*accesslist.A
|
||||
return accessLists, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// GetInheritedGrants returns grants inherited by access list accessListID from parent access lists.
|
||||
// This is not implemented in the local service.
|
||||
func (a *AccessListService) GetInheritedGrants(ctx context.Context, accessListID string) (*accesslist.Grants, error) {
|
||||
return nil, trace.NotImplemented("GetInheritedGrants should not be called")
|
||||
}
|
||||
|
||||
// ListAccessLists returns a paginated list of access lists.
|
||||
func (a *AccessListService) ListAccessLists(ctx context.Context, pageSize int, nextToken string) ([]*accesslist.AccessList, string, error) {
|
||||
return a.service.ListResources(ctx, pageSize, nextToken)
|
||||
@@ -161,73 +186,197 @@ func (a *AccessListService) GetAccessListsToReview(ctx context.Context) ([]*acce
|
||||
|
||||
// UpsertAccessList creates or updates an access list resource.
|
||||
func (a *AccessListService) UpsertAccessList(ctx context.Context, accessList *accesslist.AccessList) (*accesslist.AccessList, error) {
|
||||
op := a.service.UpsertResource
|
||||
return a.runOpWithLock(ctx, accessList, op)
|
||||
return a.runOpWithLock(ctx, accessList, opTypeUpsert)
|
||||
}
|
||||
|
||||
// UpdateAccessList updates an access list resource.
|
||||
func (a *AccessListService) UpdateAccessList(ctx context.Context, accessList *accesslist.AccessList) (*accesslist.AccessList, error) {
|
||||
op := a.service.ConditionalUpdateResource
|
||||
return a.runOpWithLock(ctx, accessList, op)
|
||||
return a.runOpWithLock(ctx, accessList, opTypeUpdate)
|
||||
}
|
||||
|
||||
type opFunc func(context.Context, *accesslist.AccessList) (*accesslist.AccessList, error)
|
||||
type opType int
|
||||
|
||||
func (a *AccessListService) runOpWithLock(ctx context.Context, accessList *accesslist.AccessList, op opFunc) (*accesslist.AccessList, error) {
|
||||
const (
|
||||
opTypeUpsert opType = iota
|
||||
opTypeUpdate
|
||||
)
|
||||
|
||||
func (a *AccessListService) runOpWithLock(ctx context.Context, accessList *accesslist.AccessList, op opType) (*accesslist.AccessList, error) {
|
||||
if err := accessList.CheckAndSetDefaults(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
var upserted *accesslist.AccessList
|
||||
var existingList *accesslist.AccessList
|
||||
|
||||
opFn := a.service.UpsertResource
|
||||
if op == opTypeUpdate {
|
||||
opFn = a.service.ConditionalUpdateResource
|
||||
}
|
||||
|
||||
validateAccessList := func() error {
|
||||
var err error
|
||||
|
||||
if op == opTypeUpdate {
|
||||
existingList, err = a.service.GetResource(ctx, accessList.GetName())
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// Set memberOf / ownerOf to the existing values to prevent them from being updated.
|
||||
accessList.Status.MemberOf = existingList.Status.MemberOf
|
||||
accessList.Status.OwnerOf = existingList.Status.OwnerOf
|
||||
} else {
|
||||
// In case the MemberOf/OwnerOf fields were manually changed, set to empty.
|
||||
accessList.Status.MemberOf = []string{}
|
||||
accessList.Status.OwnerOf = []string{}
|
||||
}
|
||||
|
||||
listMembers, err := a.memberService.WithPrefix(accessList.GetName()).GetResources(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return accesslists.ValidateAccessListWithMembers(ctx, accessList, listMembers, &accessListAndMembersGetter{a.service, a.memberService})
|
||||
}
|
||||
|
||||
updateAccessList := func() error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(accessList.GetName()), accessListLockTTL,
|
||||
func(ctx context.Context, _ backend.Backend) error {
|
||||
var err error
|
||||
upserted, err = op(ctx, accessList)
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
var err error
|
||||
upserted, err = opFn(ctx, accessList)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
reconcileOwners := func() error {
|
||||
// Create map to store owners for efficient lookup
|
||||
originalOwnersMap := make(map[string]struct{})
|
||||
if existingList != nil {
|
||||
for _, owner := range existingList.Spec.Owners {
|
||||
if owner.MembershipKind == accesslist.MembershipKindList {
|
||||
originalOwnersMap[owner.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentOwnersMap := make(map[string]struct{})
|
||||
for _, owner := range accessList.Spec.Owners {
|
||||
if owner.MembershipKind == accesslist.MembershipKindList {
|
||||
currentOwnersMap[owner.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// update references for new owners
|
||||
for ownerName := range currentOwnersMap {
|
||||
if _, exists := originalOwnersMap[ownerName]; !exists {
|
||||
if err := a.updateAccessListOwnerOf(ctx, accessList.GetName(), ownerName, true); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update references for old owners
|
||||
for ownerName := range originalOwnersMap {
|
||||
if _, exists := currentOwnersMap[ownerName]; !exists {
|
||||
if err := a.updateAccessListOwnerOf(ctx, accessList.GetName(), ownerName, false); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var actions []func() error
|
||||
|
||||
// If IGS is not enabled for this cluster we need to wrap the whole
|
||||
// operation inside *another* lock so that we can accurately count the
|
||||
// access lists in the cluster in order to prevent un-authorized use of
|
||||
// the AccessList feature
|
||||
|
||||
action := updateAccessList
|
||||
if !modules.GetModules().Features().GetEntitlement(entitlements.Identity).Enabled {
|
||||
action = func() error {
|
||||
err := a.service.RunWhileLocked(ctx, []string{createAccessListLimitLockName}, accessListLockTTL,
|
||||
func(ctx context.Context, _ backend.Backend) error {
|
||||
if err := a.VerifyAccessListCreateLimit(ctx, accessList.GetName()); err != nil {
|
||||
actions = append(actions, func() error { return a.VerifyAccessListCreateLimit(ctx, accessList.GetName()) })
|
||||
}
|
||||
|
||||
actions = append(actions, validateAccessList, updateAccessList, reconcileOwners)
|
||||
|
||||
err := a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, accessListLockTTL,
|
||||
func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(accessList.GetName()), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
for _, action := range actions {
|
||||
if err := action(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(updateAccessList())
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if err := action(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return upserted, nil
|
||||
return upserted, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// DeleteAccessList removes the specified access list resource.
|
||||
func (a *AccessListService) DeleteAccessList(ctx context.Context, name string) error {
|
||||
err := a.service.RunWhileLocked(ctx, lockName(name), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
// Delete all associated members.
|
||||
err := a.memberService.WithPrefix(name).DeleteAllResources(ctx)
|
||||
action := func(ctx context.Context, _ backend.Backend) error {
|
||||
// Get list resource.
|
||||
accessList, err := a.service.GetResource(ctx, name)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return trace.Wrap(a.service.DeleteResource(ctx, name))
|
||||
})
|
||||
// Check if the access list is a member or owner of any other access lists.
|
||||
if len(accessList.Status.MemberOf) > 0 {
|
||||
for _, memberOf := range accessList.Status.MemberOf {
|
||||
if _, err := a.service.GetResource(ctx, memberOf); err == nil {
|
||||
return trace.AccessDenied("Cannot delete '%s', as it is a member of one or more other Access Lists", accessList.Spec.Title)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(accessList.Status.OwnerOf) > 0 {
|
||||
for _, ownerOf := range accessList.Status.OwnerOf {
|
||||
if _, err := a.service.GetResource(ctx, ownerOf); err == nil {
|
||||
return trace.AccessDenied("Cannot delete '%s', as it is an owner of one or more other Access Lists", accessList.Spec.Title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trace.Wrap(err)
|
||||
// Delete all associated members.
|
||||
members, err := a.memberService.WithPrefix(name).GetResources(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.memberService.WithPrefix(name).DeleteAllResources(ctx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Update memberOf refs.
|
||||
for _, member := range members {
|
||||
if member.Spec.MembershipKind != accesslist.MembershipKindList {
|
||||
continue
|
||||
}
|
||||
if err := a.updateAccessListMemberOf(ctx, name, member.GetName(), false); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete list itself.
|
||||
if err := a.service.DeleteResource(ctx, name); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Update ownerOf refs.
|
||||
for _, owner := range accessList.Spec.Owners {
|
||||
if owner.MembershipKind != accesslist.MembershipKindList {
|
||||
continue
|
||||
}
|
||||
if err := a.updateAccessListOwnerOf(ctx, name, owner.Name, false); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return trace.Wrap(a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(name), accessListLockTTL, action)
|
||||
}))
|
||||
}
|
||||
|
||||
// DeleteAllAccessLists removes all access lists.
|
||||
@@ -248,15 +397,26 @@ func (a *AccessListService) GetSuggestedAccessLists(ctx context.Context, accessR
|
||||
}
|
||||
|
||||
// CountAccessListMembers will count all access list members.
|
||||
func (a *AccessListService) CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error) {
|
||||
func (a *AccessListService) CountAccessListMembers(ctx context.Context, accessListName string) (users uint32, lists uint32, err error) {
|
||||
count := uint(0)
|
||||
err := a.service.RunWhileLocked(ctx, lockName(accessListName), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
listCount := uint(0)
|
||||
err = a.service.RunWhileLocked(ctx, lockName(accessListName), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
var err error
|
||||
count, err = a.memberService.WithPrefix(accessListName).CountResources(ctx)
|
||||
return trace.Wrap(err)
|
||||
members, err := a.memberService.WithPrefix(accessListName).GetResources(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
for _, member := range members {
|
||||
if member.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
listCount++
|
||||
} else {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return uint32(count), trace.Wrap(err)
|
||||
return uint32(count), uint32(listCount), trace.Wrap(err)
|
||||
}
|
||||
|
||||
// ListAccessListMembers returns a paginated list of all access list members.
|
||||
@@ -303,71 +463,198 @@ func (a *AccessListService) GetAccessListMember(ctx context.Context, accessList
|
||||
return member, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UpsertAccessListMember creates or updates an access list member resource.
|
||||
func (a *AccessListService) UpsertAccessListMember(ctx context.Context, member *accesslist.AccessListMember) (*accesslist.AccessListMember, error) {
|
||||
var upserted *accesslist.AccessListMember
|
||||
err := a.service.RunWhileLocked(ctx, lockName(member.Spec.AccessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
_, err := a.service.GetResource(ctx, member.Spec.AccessList)
|
||||
// updateAccessListRefField is a helper that updates the specified field (memberOf or ownerOf) of an Access List,
|
||||
// adding or removing the specified accessListName to the field of targetName.
|
||||
func (a *AccessListService) updateAccessListRefField(
|
||||
ctx context.Context,
|
||||
accessListName string,
|
||||
targetName string,
|
||||
new bool,
|
||||
fieldSelector func(status *accesslist.Status) *[]string,
|
||||
) error {
|
||||
targetAccessList, err := a.service.GetResource(ctx, targetName)
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
// If list is not found, it's possible that it was deleted. Regardless, there's nothing to update.
|
||||
return nil
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
field := fieldSelector(&targetAccessList.Status)
|
||||
|
||||
// If the field already contains the Access List, and we're adding,
|
||||
// or doesn't contain it, and we're removing, there's nothing to do.
|
||||
if slices.Contains(*field, accessListName) == new {
|
||||
return nil
|
||||
}
|
||||
|
||||
if new {
|
||||
*field = append(*field, accessListName)
|
||||
} else {
|
||||
*field = slices.DeleteFunc(*field, func(e string) bool {
|
||||
return e == accessListName
|
||||
})
|
||||
}
|
||||
|
||||
if _, err := a.service.UpdateResource(ctx, targetAccessList); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateAccessListMemberOf updates the memberOf field for the specified memberName and accessListName.
|
||||
// Should only be called after the relevant member has been successfully upserted or deleted.
|
||||
func (a *AccessListService) updateAccessListMemberOf(ctx context.Context, accessListName, memberName string, new bool) error {
|
||||
return a.updateAccessListRefField(ctx, accessListName, memberName, new, func(status *accesslist.Status) *[]string {
|
||||
return &status.MemberOf
|
||||
})
|
||||
}
|
||||
|
||||
// updateAccessListOwnerOf updates the ownerOf field for the specified ownerName and accessListName.
|
||||
// Should only be called after the relevant owner has been successfully upserted or deleted.
|
||||
func (a *AccessListService) updateAccessListOwnerOf(ctx context.Context, accessListName, ownerName string, new bool) error {
|
||||
return a.updateAccessListRefField(ctx, accessListName, ownerName, new, func(status *accesslist.Status) *[]string {
|
||||
return &status.OwnerOf
|
||||
})
|
||||
}
|
||||
|
||||
// GetAccessListOwners returns a list of all owners in an Access List, including those inherited from nested Access Lists.
|
||||
//
|
||||
// Returned Owners are not validated for ownership requirements – use `IsAccessListOwner` for validation.
|
||||
func (a *AccessListService) GetAccessListOwners(ctx context.Context, accessListName string) ([]*accesslist.Owner, error) {
|
||||
var owners []*accesslist.Owner
|
||||
err := a.service.RunWhileLocked(ctx, lockName(accessListName), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
accessList, err := a.service.GetResource(ctx, accessListName)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
upserted, err = a.memberService.WithPrefix(member.Spec.AccessList).UpsertResource(ctx, member)
|
||||
owners, err = accesslists.GetOwnersFor(ctx, accessList, &accessListAndMembersGetter{a.service, a.memberService})
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return owners, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UpsertAccessListMember creates or updates an access list member resource.
|
||||
func (a *AccessListService) UpsertAccessListMember(ctx context.Context, member *accesslist.AccessListMember) (*accesslist.AccessListMember, error) {
|
||||
var upserted *accesslist.AccessListMember
|
||||
action := func(ctx context.Context, _ backend.Backend) error {
|
||||
memberList, err := a.service.GetResource(ctx, member.Spec.AccessList)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := accesslists.ValidateAccessListMember(ctx, memberList, member, &accessListAndMembersGetter{a.service, a.memberService}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
upserted, err = a.memberService.WithPrefix(member.Spec.AccessList).UpsertResource(ctx, member)
|
||||
|
||||
if err == nil && member.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
if err := a.updateAccessListMemberOf(ctx, member.Spec.AccessList, member.Spec.Name, true); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return upserted, nil
|
||||
|
||||
err := a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(member.Spec.AccessList), accessListLockTTL, action)
|
||||
})
|
||||
return upserted, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UpdateAccessListMember conditionally updates an access list member resource.
|
||||
func (a *AccessListService) UpdateAccessListMember(ctx context.Context, member *accesslist.AccessListMember) (*accesslist.AccessListMember, error) {
|
||||
var updated *accesslist.AccessListMember
|
||||
err := a.service.RunWhileLocked(ctx, lockName(member.Spec.AccessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
_, err := a.service.GetResource(ctx, member.Spec.AccessList)
|
||||
if err != nil {
|
||||
err := a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(member.Spec.AccessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
memberList, err := a.service.GetResource(ctx, member.Spec.AccessList)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := accesslists.ValidateAccessListMember(ctx, memberList, member, &accessListAndMembersGetter{a.service, a.memberService}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
updated, err = a.memberService.WithPrefix(member.Spec.AccessList).ConditionalUpdateResource(ctx, member)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
updated, err = a.memberService.WithPrefix(member.Spec.AccessList).ConditionalUpdateResource(ctx, member)
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return updated, nil
|
||||
return updated, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// DeleteAccessListMember hard deletes the specified access list member resource.
|
||||
func (a *AccessListService) DeleteAccessListMember(ctx context.Context, accessList string, memberName string) error {
|
||||
err := a.service.RunWhileLocked(ctx, lockName(accessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
_, err := a.service.GetResource(ctx, accessList)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(a.memberService.WithPrefix(accessList).DeleteResource(ctx, memberName))
|
||||
err := a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(accessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
_, err := a.service.GetResource(ctx, accessList)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
member, err := a.memberService.WithPrefix(accessList).GetResource(ctx, memberName)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.memberService.WithPrefix(accessList).DeleteResource(ctx, memberName); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if member.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
if err := a.updateAccessListMemberOf(ctx, accessList, memberName, false); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// DeleteAllAccessListMembersForAccessList hard deletes all access list members
|
||||
// for an access list. Note that deleting all members is the only member
|
||||
// operation allowed on a list with implicit membership, as it provides a
|
||||
// mechanism for cleaning out the user list if a list is converted from explicit
|
||||
// to implicit.
|
||||
// for an access list. Note that deleting all members is the only member operation
|
||||
// allowed on a list with implicit membership, as it provides a mechanism for
|
||||
// cleaning out the user list if a list is converted from explicit to implicit.
|
||||
func (a *AccessListService) DeleteAllAccessListMembersForAccessList(ctx context.Context, accessList string) error {
|
||||
err := a.service.RunWhileLocked(ctx, lockName(accessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
_, err := a.service.GetResource(ctx, accessList)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(a.memberService.WithPrefix(accessList).DeleteAllResources(ctx))
|
||||
err := a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(accessList), accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
_, err := a.service.GetResource(ctx, accessList)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
allMembers, err := a.memberService.WithPrefix(accessList).GetResources(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.memberService.WithPrefix(accessList).DeleteAllResources(ctx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
for _, member := range allMembers {
|
||||
if member.Spec.MembershipKind != accesslist.MembershipKindList {
|
||||
continue
|
||||
}
|
||||
if err := a.updateAccessListMemberOf(ctx, accessList, member.Spec.Name, false); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// DeleteAllAccessListMembers hard deletes all access list members.
|
||||
func (a *AccessListService) DeleteAllAccessListMembers(ctx context.Context) error {
|
||||
|
||||
// Locks are not used here as this operation is more likely to be used by the cache.
|
||||
return trace.Wrap(a.memberService.DeleteAllResources(ctx))
|
||||
}
|
||||
@@ -384,98 +671,148 @@ func (a *AccessListService) UpsertAccessListWithMembers(ctx context.Context, acc
|
||||
}
|
||||
}
|
||||
|
||||
validateAccessList := func() error {
|
||||
existingList, err := a.service.GetResource(ctx, accessList.GetName())
|
||||
if err != nil && !trace.IsNotFound(err) {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if existingList != nil {
|
||||
accessList.Status.MemberOf = existingList.Status.MemberOf
|
||||
accessList.Status.OwnerOf = existingList.Status.OwnerOf
|
||||
} else {
|
||||
// In case the MemberOf/OwnerOf fields were manually changed, set to empty.
|
||||
accessList.Status.MemberOf = []string{}
|
||||
accessList.Status.OwnerOf = []string{}
|
||||
}
|
||||
|
||||
if err := accesslists.ValidateAccessListWithMembers(ctx, accessList, membersIn, &accessListAndMembersGetter{a.service, a.memberService}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
reconcileMembers := func() error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(accessList.GetName()), 2*accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
// Convert the members slice to a map for easier lookup.
|
||||
membersMap := utils.FromSlice(membersIn, types.GetName)
|
||||
// Convert the members slice to a map for easier lookup.
|
||||
membersMap := utils.FromSlice(membersIn, types.GetName)
|
||||
|
||||
var (
|
||||
members []*accesslist.AccessListMember
|
||||
membersToken string
|
||||
)
|
||||
var (
|
||||
members []*accesslist.AccessListMember
|
||||
membersToken string
|
||||
)
|
||||
|
||||
for {
|
||||
// List all members for the access list.
|
||||
var err error
|
||||
members, membersToken, err = a.memberService.WithPrefix(accessList.GetName()).ListResources(ctx, 0 /* default size */, membersToken)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
for {
|
||||
// List all members for the access list.
|
||||
var err error
|
||||
members, membersToken, err = a.memberService.WithPrefix(accessList.GetName()).ListResources(ctx, 0 /* default size */, membersToken)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
for _, existingMember := range members {
|
||||
// If the member is not in the new members map (request), delete it.
|
||||
if newMember, ok := membersMap[existingMember.GetName()]; !ok {
|
||||
err = a.memberService.WithPrefix(accessList.GetName()).DeleteResource(ctx, existingMember.GetName())
|
||||
for _, existingMember := range members {
|
||||
// If the member is not in the new members map (request), delete it.
|
||||
if newMember, ok := membersMap[existingMember.GetName()]; !ok {
|
||||
err = a.memberService.WithPrefix(accessList.GetName()).DeleteResource(ctx, existingMember.GetName())
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// Update memberOf field if nested list.
|
||||
if existingMember.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
if err := a.updateAccessListMemberOf(ctx, accessList.GetName(), existingMember.GetName(), false); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Preserve the membership metadata for any existing members
|
||||
// to suppress member records flipping back and forth due
|
||||
// due SCIM pushes or Sync Service updates.
|
||||
if !existingMember.Spec.Expires.IsZero() {
|
||||
newMember.Spec.Expires = existingMember.Spec.Expires
|
||||
}
|
||||
if existingMember.Spec.Reason != "" {
|
||||
newMember.Spec.Reason = existingMember.Spec.Reason
|
||||
}
|
||||
newMember.Spec.AddedBy = existingMember.Spec.AddedBy
|
||||
|
||||
// Compare members and update if necessary.
|
||||
if !cmp.Equal(newMember, existingMember) {
|
||||
// Update the member.
|
||||
upserted, err := a.memberService.WithPrefix(accessList.GetName()).UpsertResource(ctx, newMember)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
// Preserve the membership metadata for any existing members
|
||||
// to suppress member records flipping back and forth due
|
||||
// due SCIM pushes or Sync Service updates.
|
||||
if !existingMember.Spec.Expires.IsZero() {
|
||||
newMember.Spec.Expires = existingMember.Spec.Expires
|
||||
}
|
||||
if existingMember.Spec.Reason != "" {
|
||||
newMember.Spec.Reason = existingMember.Spec.Reason
|
||||
}
|
||||
newMember.Spec.AddedBy = existingMember.Spec.AddedBy
|
||||
|
||||
// Compare members and update if necessary.
|
||||
if !cmp.Equal(newMember, existingMember) {
|
||||
// Update the member.
|
||||
upserted, err := a.memberService.WithPrefix(accessList.GetName()).UpsertResource(ctx, newMember)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
existingMember.SetRevision(upserted.GetRevision())
|
||||
}
|
||||
existingMember.SetRevision(upserted.GetRevision())
|
||||
}
|
||||
|
||||
// Remove the member from the map.
|
||||
delete(membersMap, existingMember.GetName())
|
||||
}
|
||||
|
||||
if membersToken == "" {
|
||||
break
|
||||
}
|
||||
// Remove the member from the map.
|
||||
delete(membersMap, existingMember.GetName())
|
||||
}
|
||||
|
||||
// Add any remaining members to the access list.
|
||||
for _, member := range membersMap {
|
||||
upserted, err := a.memberService.WithPrefix(accessList.GetName()).UpsertResource(ctx, member)
|
||||
if err != nil {
|
||||
if membersToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining members to the access list.
|
||||
for _, member := range membersMap {
|
||||
upserted, err := a.memberService.WithPrefix(accessList.GetName()).UpsertResource(ctx, member)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// Update memberOf field if nested list.
|
||||
if member.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
if err := a.updateAccessListMemberOf(ctx, accessList.GetName(), member.Spec.Name, true); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
member.SetRevision(upserted.GetRevision())
|
||||
}
|
||||
member.SetRevision(upserted.GetRevision())
|
||||
}
|
||||
|
||||
var err error
|
||||
accessList, err = a.service.UpsertResource(ctx, accessList)
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
reconcileOwners := func() error {
|
||||
// update references for new owners
|
||||
for _, owner := range accessList.Spec.Owners {
|
||||
if owner.MembershipKind == accesslist.MembershipKindList {
|
||||
if err := a.updateAccessListOwnerOf(ctx, accessList.GetName(), owner.Name, true); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
updateAccessList := func() error {
|
||||
var err error
|
||||
accessList, err = a.service.UpsertResource(ctx, accessList)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
var actions []func() error
|
||||
|
||||
// If IGS is not enabled for this cluster we need to wrap the whole update and
|
||||
// member reconciliation in *another* lock so that we can accurately count the
|
||||
// access lists in the cluster in order to prevent un-authorized use of the
|
||||
// AccessList feature
|
||||
|
||||
action := reconcileMembers
|
||||
if !modules.GetModules().Features().GetEntitlement(entitlements.Identity).Enabled {
|
||||
action = func() error {
|
||||
return a.service.RunWhileLocked(ctx, []string{createAccessListLimitLockName}, 2*accessListLockTTL,
|
||||
func(ctx context.Context, _ backend.Backend) error {
|
||||
if err := a.VerifyAccessListCreateLimit(ctx, accessList.GetName()); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(reconcileMembers())
|
||||
})
|
||||
}
|
||||
actions = append(actions, func() error { return a.VerifyAccessListCreateLimit(ctx, accessList.GetName()) })
|
||||
}
|
||||
|
||||
if err := action(); err != nil {
|
||||
actions = append(actions, validateAccessList, reconcileMembers, updateAccessList, reconcileOwners)
|
||||
|
||||
if err := a.service.RunWhileLocked(ctx, []string{accessListResourceLockName}, 2*accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
return a.service.RunWhileLocked(ctx, lockName(accessList.GetName()), 2*accessListLockTTL, func(ctx context.Context, _ backend.Backend) error {
|
||||
for _, action := range actions {
|
||||
if err := action(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -664,7 +1001,7 @@ func (a *AccessListService) VerifyAccessListCreateLimit(ctx context.Context, tar
|
||||
return nil // unlimited
|
||||
}
|
||||
|
||||
lists, err := a.GetAccessLists(ctx)
|
||||
lists, err := a.service.GetResources(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -132,9 +132,24 @@ func (a *AccessListService) DeleteAllAccessLists(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// CountAccessListMembers will count all access list members.
|
||||
func (a *AccessListService) CountAccessListMembers(ctx context.Context, accessListName string) (uint32, error) {
|
||||
count, err := a.memberService.WithPrefix(accessListName).CountResources(ctx)
|
||||
return uint32(count), trace.Wrap(err)
|
||||
func (a *AccessListService) CountAccessListMembers(ctx context.Context, accessListName string) (uint32, uint32, error) {
|
||||
members, err := a.memberService.WithPrefix(accessListName).GetResources(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, trace.Wrap(err)
|
||||
}
|
||||
|
||||
var count uint32
|
||||
var listCount uint32
|
||||
|
||||
for _, member := range members {
|
||||
if member.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
listCount++
|
||||
} else {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count, listCount, nil
|
||||
}
|
||||
|
||||
// ListAccessListMembers returns a paginated list of all access list members.
|
||||
@@ -189,3 +204,27 @@ func (a *AccessListService) ListAllAccessListMembers(ctx context.Context, pageSi
|
||||
members, nextToken, err := a.memberService.ListResources(ctx, pageSize, pageToken)
|
||||
return members, nextToken, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UnconditionalUpsertAccessList creates or updates an Access List resource without any validation.
|
||||
// It should only ever be used by the cache.
|
||||
func (a *AccessListService) UnconditionalUpsertAccessList(ctx context.Context, accessList *accesslist.AccessList) (*accesslist.AccessList, error) {
|
||||
return a.service.UpsertResource(ctx, accessList)
|
||||
}
|
||||
|
||||
// UnconditionalDeleteAccessList removes the specified Access List resource without any validation.
|
||||
// It should only ever be used by the cache.
|
||||
func (a *AccessListService) UnconditionalDeleteAccessList(ctx context.Context, name string) error {
|
||||
return a.service.DeleteResource(ctx, name)
|
||||
}
|
||||
|
||||
// UnconditionalUpsertAccessListMember creates or updates an Access List Member resource without any validation.
|
||||
// It should only ever be used by the cache.
|
||||
func (a *AccessListService) UnconditionalUpsertAccessListMember(ctx context.Context, member *accesslist.AccessListMember) (*accesslist.AccessListMember, error) {
|
||||
return a.memberService.WithPrefix(member.Spec.AccessList).UpsertResource(ctx, member)
|
||||
}
|
||||
|
||||
// UnconditionalDeleteAccessListMember removes the specified Access List Member resource without any validation.
|
||||
// It should only ever be used by the cache.
|
||||
func (a *AccessListService) UnconditionalDeleteAccessListMember(ctx context.Context, accessList, memberName string) error {
|
||||
return a.memberService.WithPrefix(accessList).DeleteResource(ctx, memberName)
|
||||
}
|
||||
|
||||
@@ -749,7 +749,8 @@ func (e *AccessListMemberCreateEvent) Anonymize(a utils.Anonymizer) prehogv1a.Su
|
||||
return prehogv1a.SubmitEventRequest{
|
||||
Event: &prehogv1a.SubmitEventRequest_AccessListMemberCreate{
|
||||
AccessListMemberCreate: &prehogv1a.AccessListMemberCreateEvent{
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
MemberKind: e.MemberKind,
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: a.AnonymizeString(e.Metadata.Id),
|
||||
},
|
||||
@@ -765,7 +766,8 @@ func (e *AccessListMemberUpdateEvent) Anonymize(a utils.Anonymizer) prehogv1a.Su
|
||||
return prehogv1a.SubmitEventRequest{
|
||||
Event: &prehogv1a.SubmitEventRequest_AccessListMemberUpdate{
|
||||
AccessListMemberUpdate: &prehogv1a.AccessListMemberUpdateEvent{
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
MemberKind: e.MemberKind,
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: a.AnonymizeString(e.Metadata.Id),
|
||||
},
|
||||
@@ -781,7 +783,8 @@ func (e *AccessListMemberDeleteEvent) Anonymize(a utils.Anonymizer) prehogv1a.Su
|
||||
return prehogv1a.SubmitEventRequest{
|
||||
Event: &prehogv1a.SubmitEventRequest_AccessListMemberDelete{
|
||||
AccessListMemberDelete: &prehogv1a.AccessListMemberDeleteEvent{
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
MemberKind: e.MemberKind,
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: a.AnonymizeString(e.Metadata.Id),
|
||||
},
|
||||
@@ -797,9 +800,11 @@ func (e *AccessListGrantsToUserEvent) Anonymize(a utils.Anonymizer) prehogv1a.Su
|
||||
return prehogv1a.SubmitEventRequest{
|
||||
Event: &prehogv1a.SubmitEventRequest_AccessListGrantsToUser{
|
||||
AccessListGrantsToUser: &prehogv1a.AccessListGrantsToUserEvent{
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
CountRolesGranted: e.CountRolesGranted,
|
||||
CountTraitsGranted: e.CountTraitsGranted,
|
||||
UserName: a.AnonymizeString(e.UserName),
|
||||
CountRolesGranted: e.CountRolesGranted,
|
||||
CountTraitsGranted: e.CountTraitsGranted,
|
||||
CountInheritedRolesGranted: e.CountInheritedRolesGranted,
|
||||
CountInheritedTraitsGranted: e.CountInheritedTraitsGranted,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1677,6 +1682,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, userMD UserMetadata
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: e.AccessListMemberCreate.Metadata.Id,
|
||||
},
|
||||
MemberKind: e.AccessListMemberCreate.MemberMetadata.MembershipKind.String(),
|
||||
}
|
||||
return ret, nil
|
||||
case *usageeventsv1.UsageEventOneOf_AccessListMemberUpdate:
|
||||
@@ -1685,6 +1691,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, userMD UserMetadata
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: e.AccessListMemberUpdate.Metadata.Id,
|
||||
},
|
||||
MemberKind: e.AccessListMemberUpdate.MemberMetadata.MembershipKind.String(),
|
||||
}
|
||||
return ret, nil
|
||||
case *usageeventsv1.UsageEventOneOf_AccessListMemberDelete:
|
||||
@@ -1693,13 +1700,16 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, userMD UserMetadata
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: e.AccessListMemberDelete.Metadata.Id,
|
||||
},
|
||||
MemberKind: e.AccessListMemberDelete.MemberMetadata.MembershipKind.String(),
|
||||
}
|
||||
return ret, nil
|
||||
case *usageeventsv1.UsageEventOneOf_AccessListGrantsToUser:
|
||||
ret := &AccessListGrantsToUserEvent{
|
||||
UserName: userMD.Username,
|
||||
CountRolesGranted: e.AccessListGrantsToUser.CountRolesGranted,
|
||||
CountTraitsGranted: e.AccessListGrantsToUser.CountTraitsGranted,
|
||||
UserName: userMD.Username,
|
||||
CountRolesGranted: e.AccessListGrantsToUser.CountRolesGranted,
|
||||
CountTraitsGranted: e.AccessListGrantsToUser.CountTraitsGranted,
|
||||
CountInheritedRolesGranted: e.AccessListGrantsToUser.CountInheritedRolesGranted,
|
||||
CountInheritedTraitsGranted: e.AccessListGrantsToUser.CountInheritedTraitsGranted,
|
||||
}
|
||||
return ret, nil
|
||||
case *usageeventsv1.UsageEventOneOf_TagExecuteQuery:
|
||||
|
||||
@@ -25,7 +25,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
usageeventsv1 "github.com/gravitational/teleport/api/gen/proto/go/usageevents/v1"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
prehogv1a "github.com/gravitational/teleport/gen/proto/go/prehog/v1alpha"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
@@ -448,6 +450,9 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
Metadata: &usageeventsv1.AccessListMetadata{
|
||||
Id: "someid",
|
||||
},
|
||||
MemberMetadata: &usageeventsv1.AccessListMemberMetadata{
|
||||
MembershipKind: accesslistv1.MembershipKind_MEMBERSHIP_KIND_USER,
|
||||
},
|
||||
},
|
||||
}},
|
||||
identityUsername: "myuser",
|
||||
@@ -458,6 +463,7 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: expectedAnonymizedAccessListIDString,
|
||||
},
|
||||
MemberKind: accesslist.MembershipKindUser,
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -468,6 +474,9 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
Metadata: &usageeventsv1.AccessListMetadata{
|
||||
Id: "someid",
|
||||
},
|
||||
MemberMetadata: &usageeventsv1.AccessListMemberMetadata{
|
||||
MembershipKind: accesslistv1.MembershipKind_MEMBERSHIP_KIND_USER,
|
||||
},
|
||||
},
|
||||
}},
|
||||
identityUsername: "myuser",
|
||||
@@ -478,6 +487,7 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: expectedAnonymizedAccessListIDString,
|
||||
},
|
||||
MemberKind: accesslist.MembershipKindUser,
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -488,6 +498,9 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
Metadata: &usageeventsv1.AccessListMetadata{
|
||||
Id: "someid",
|
||||
},
|
||||
MemberMetadata: &usageeventsv1.AccessListMemberMetadata{
|
||||
MembershipKind: accesslistv1.MembershipKind_MEMBERSHIP_KIND_USER,
|
||||
},
|
||||
},
|
||||
}},
|
||||
identityUsername: "myuser",
|
||||
@@ -498,6 +511,7 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
Metadata: &prehogv1a.AccessListMetadata{
|
||||
Id: expectedAnonymizedAccessListIDString,
|
||||
},
|
||||
MemberKind: accesslist.MembershipKindUser,
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -505,17 +519,21 @@ func TestConvertUsageEvent(t *testing.T) {
|
||||
name: "access list grants to user event",
|
||||
event: &usageeventsv1.UsageEventOneOf{Event: &usageeventsv1.UsageEventOneOf_AccessListGrantsToUser{
|
||||
AccessListGrantsToUser: &usageeventsv1.AccessListGrantsToUser{
|
||||
CountRolesGranted: 5,
|
||||
CountTraitsGranted: 6,
|
||||
CountRolesGranted: 5,
|
||||
CountTraitsGranted: 6,
|
||||
CountInheritedRolesGranted: 0,
|
||||
CountInheritedTraitsGranted: 0,
|
||||
},
|
||||
}},
|
||||
identityUsername: "myuser",
|
||||
errCheck: require.NoError,
|
||||
expected: &prehogv1a.SubmitEventRequest{Event: &prehogv1a.SubmitEventRequest_AccessListGrantsToUser{
|
||||
AccessListGrantsToUser: &prehogv1a.AccessListGrantsToUserEvent{
|
||||
UserName: expectedAnonymizedUserString,
|
||||
CountRolesGranted: 5,
|
||||
CountTraitsGranted: 6,
|
||||
UserName: expectedAnonymizedUserString,
|
||||
CountRolesGranted: 5,
|
||||
CountTraitsGranted: 6,
|
||||
CountInheritedRolesGranted: 0,
|
||||
CountInheritedTraitsGranted: 0,
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
||||
@@ -934,6 +934,8 @@ message AccessListMemberCreateEvent {
|
||||
// user_name is the anonymized user name
|
||||
string user_name = 1;
|
||||
AccessListMetadata metadata = 2;
|
||||
// member_kind is the type of membership of the created member in the parent access list.
|
||||
string member_kind = 3;
|
||||
}
|
||||
|
||||
// AccessListMemberUpdate is an event that is emitted when a member is updated in an access list.
|
||||
@@ -941,6 +943,8 @@ message AccessListMemberUpdateEvent {
|
||||
// user_name is the anonymized user name
|
||||
string user_name = 1;
|
||||
AccessListMetadata metadata = 2;
|
||||
// membership_kind is the type of membership of the updated member in the parent access list.
|
||||
string member_kind = 3;
|
||||
}
|
||||
|
||||
// AccessListMemberDelete is an event that is emitted when a member is removed from an access list.
|
||||
@@ -948,6 +952,8 @@ message AccessListMemberDeleteEvent {
|
||||
// user_name is anonymized user name
|
||||
string user_name = 1;
|
||||
AccessListMetadata metadata = 2;
|
||||
// member_kind is the type of membership of the deleted user in the parent access list.
|
||||
string member_kind = 3;
|
||||
}
|
||||
|
||||
// AccessListGrantsToUser is an event that is emitted when access list permissions are granted to a user
|
||||
@@ -961,6 +967,12 @@ message AccessListGrantsToUserEvent {
|
||||
|
||||
// count_traits_granted is the number of traits granted to a user.
|
||||
int32 count_traits_granted = 3;
|
||||
|
||||
// count_inherited_roles_granted is the number of roles granted to a user inherited from nested access lists.
|
||||
int32 count_inherited_roles_granted = 4;
|
||||
|
||||
// count_inherited_traits_granted is the number of traits granted to a user inherited from nested access lists.
|
||||
int32 count_inherited_traits_granted = 5;
|
||||
}
|
||||
|
||||
// AccessListReviewCreateEvent is an event that is emitted when an access list review is created.
|
||||
|
||||
@@ -49,6 +49,8 @@ type ACLCommand struct {
|
||||
|
||||
// Used for managing a particular access list.
|
||||
accessListName string
|
||||
// Used to add an access list to another one
|
||||
memberKind string
|
||||
|
||||
// Used for managing membership to an access list.
|
||||
userName string
|
||||
@@ -56,6 +58,11 @@ type ACLCommand struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
const (
|
||||
memberKindUser = "user"
|
||||
memberKindList = "list"
|
||||
)
|
||||
|
||||
// Initialize allows ACLCommand to plug itself into the CLI parser
|
||||
func (c *ACLCommand) Initialize(app *kingpin.Application, _ *servicecfg.Config) {
|
||||
acl := app.Command("acl", "Manage access lists.").Alias("access-lists")
|
||||
@@ -70,6 +77,7 @@ func (c *ACLCommand) Initialize(app *kingpin.Application, _ *servicecfg.Config)
|
||||
users := acl.Command("users", "Manage user membership to access lists.")
|
||||
|
||||
c.usersAdd = users.Command("add", "Add a user to an access list.")
|
||||
c.usersAdd.Flag("kind", "Access list member kind, 'user' or 'list'").Default(memberKindUser).EnumVar(&c.memberKind, memberKindUser, memberKindList)
|
||||
c.usersAdd.Arg("access-list-name", "The access list name.").Required().StringVar(&c.accessListName)
|
||||
c.usersAdd.Arg("user", "The user to add to the access list.").Required().StringVar(&c.userName)
|
||||
c.usersAdd.Arg("expires", "When the user's access expires (must be in RFC3339). Defaults to the expiration time of the access list.").StringVar(&c.expires)
|
||||
@@ -151,6 +159,14 @@ func (c *ACLCommand) UsersAdd(ctx context.Context, client *authclient.Client) er
|
||||
}
|
||||
}
|
||||
|
||||
var membershipKind string
|
||||
switch c.memberKind {
|
||||
case memberKindList:
|
||||
membershipKind = accesslist.MembershipKindList
|
||||
case "", memberKindUser:
|
||||
membershipKind = accesslist.MembershipKindUser
|
||||
}
|
||||
|
||||
member, err := accesslist.NewAccessListMember(header.Metadata{
|
||||
Name: c.userName,
|
||||
}, accesslist.AccessListMemberSpec{
|
||||
@@ -160,8 +176,9 @@ func (c *ACLCommand) UsersAdd(ctx context.Context, client *authclient.Client) er
|
||||
Expires: expires,
|
||||
|
||||
// The following fields will be updated in the backend, so their values here don't matter.
|
||||
Joined: time.Now(),
|
||||
AddedBy: "dummy",
|
||||
Joined: time.Now(),
|
||||
AddedBy: "dummy",
|
||||
MembershipKind: membershipKind,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
@@ -219,7 +236,11 @@ func (c *ACLCommand) UsersList(ctx context.Context, client *authclient.Client) e
|
||||
}
|
||||
fmt.Printf("Members of %s:\n", c.accessListName)
|
||||
for _, member := range allMembers {
|
||||
fmt.Printf("- %s\n", member.Spec.Name)
|
||||
if member.Spec.MembershipKind == accesslist.MembershipKindList {
|
||||
fmt.Printf("- (Access List) %s \n", member.Spec.Name)
|
||||
} else {
|
||||
fmt.Printf("- %s\n", member.Spec.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
@@ -249,6 +270,7 @@ func displayAccessListsText(accessLists ...*accesslist.AccessList) error {
|
||||
for k, values := range accessList.GetGrants().Traits {
|
||||
traitStrings = append(traitStrings, fmt.Sprintf("%s:{%s}", k, strings.Join(values, ",")))
|
||||
}
|
||||
|
||||
grantedTraits := strings.Join(traitStrings, ",")
|
||||
table.AddRow([]string{
|
||||
accessList.GetName(),
|
||||
|
||||
@@ -446,7 +446,7 @@ func (p *cliModules) GenerateAccessRequestPromotions(_ context.Context, _ module
|
||||
return &types.AccessRequestAllowedPromotions{}, nil
|
||||
}
|
||||
|
||||
func (p *cliModules) GetSuggestedAccessLists(ctx context.Context, _ *tlsca.Identity, _ modules.AccessListSuggestionClient, _ modules.AccessListGetter, _ string) ([]*accesslist.AccessList, error) {
|
||||
func (p *cliModules) GetSuggestedAccessLists(ctx context.Context, _ *tlsca.Identity, _ modules.AccessListSuggestionClient, _ modules.AccessListAndMembersGetter, _ string) ([]*accesslist.AccessList, error) {
|
||||
return []*accesslist.AccessList{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ func (p *cliModules) GenerateAccessRequestPromotions(_ context.Context, _ module
|
||||
return &types.AccessRequestAllowedPromotions{}, nil
|
||||
}
|
||||
|
||||
func (p *cliModules) GetSuggestedAccessLists(ctx context.Context, _ *tlsca.Identity, _ modules.AccessListSuggestionClient, _ modules.AccessListGetter, _ string) ([]*accesslist.AccessList, error) {
|
||||
func (p *cliModules) GetSuggestedAccessLists(ctx context.Context, _ *tlsca.Identity, _ modules.AccessListSuggestionClient, _ modules.AccessListAndMembersGetter, _ string) ([]*accesslist.AccessList, error) {
|
||||
return []*accesslist.AccessList{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import parseError, { ApiError } from './parseError';
|
||||
export const MFA_HEADER = 'Teleport-Mfa-Response';
|
||||
|
||||
const api = {
|
||||
get(url, abortSignal?) {
|
||||
get(url: string, abortSignal?: AbortSignal) {
|
||||
return api.fetchJsonWithMfaAuthnRetry(url, { signal: abortSignal });
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user