mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
scoped watcher/caching prep (#68320)
This commit is contained in:
@@ -176,12 +176,17 @@ type ListScopedRolesRequest struct {
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
|
||||
// ResourceScope filters roles by their resource scope if specified.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
ResourceScope *v1.Filter `protobuf:"bytes,3,opt,name=resource_scope,json=resourceScope,proto3" json:"resource_scope,omitempty"`
|
||||
// AssignableScope filters roles by their assignable scope if specified.
|
||||
AssignableScope *v1.Filter `protobuf:"bytes,4,opt,name=assignable_scope,json=assignableScope,proto3" json:"assignable_scope,omitempty"`
|
||||
// NameFilter filters roles by their name using a case-insensitive substring
|
||||
// match if specified.
|
||||
NameFilter string `protobuf:"bytes,5,opt,name=name_filter,json=nameFilter,proto3" json:"name_filter,omitempty"`
|
||||
NameFilter string `protobuf:"bytes,5,opt,name=name_filter,json=nameFilter,proto3" json:"name_filter,omitempty"`
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter `protobuf:"bytes,6,opt,name=scope_filter,json=scopeFilter,proto3" json:"scope_filter,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -225,6 +230,7 @@ func (x *ListScopedRolesRequest) GetPageToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) GetResourceScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.ResourceScope
|
||||
@@ -232,13 +238,6 @@ func (x *ListScopedRolesRequest) GetResourceScope() *v1.Filter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) GetAssignableScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.AssignableScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) GetNameFilter() string {
|
||||
if x != nil {
|
||||
return x.NameFilter
|
||||
@@ -246,6 +245,13 @@ func (x *ListScopedRolesRequest) GetNameFilter() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) GetScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.ScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetPageSize(v int32) {
|
||||
x.PageSize = v
|
||||
}
|
||||
@@ -254,18 +260,20 @@ func (x *ListScopedRolesRequest) SetPageToken(v string) {
|
||||
x.PageToken = v
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) SetResourceScope(v *v1.Filter) {
|
||||
x.ResourceScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetAssignableScope(v *v1.Filter) {
|
||||
x.AssignableScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetNameFilter(v string) {
|
||||
x.NameFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetScopeFilter(v *v1.Filter) {
|
||||
x.ScopeFilter = v
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) HasResourceScope() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
@@ -273,19 +281,20 @@ func (x *ListScopedRolesRequest) HasResourceScope() bool {
|
||||
return x.ResourceScope != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) HasAssignableScope() bool {
|
||||
func (x *ListScopedRolesRequest) HasScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.AssignableScope != nil
|
||||
return x.ScopeFilter != nil
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) ClearResourceScope() {
|
||||
x.ResourceScope = nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) ClearAssignableScope() {
|
||||
x.AssignableScope = nil
|
||||
func (x *ListScopedRolesRequest) ClearScopeFilter() {
|
||||
x.ScopeFilter = nil
|
||||
}
|
||||
|
||||
type ListScopedRolesRequest_builder struct {
|
||||
@@ -296,12 +305,17 @@ type ListScopedRolesRequest_builder struct {
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
PageToken string
|
||||
// ResourceScope filters roles by their resource scope if specified.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
ResourceScope *v1.Filter
|
||||
// AssignableScope filters roles by their assignable scope if specified.
|
||||
AssignableScope *v1.Filter
|
||||
// NameFilter filters roles by their name using a case-insensitive substring
|
||||
// match if specified.
|
||||
NameFilter string
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter
|
||||
}
|
||||
|
||||
func (b0 ListScopedRolesRequest_builder) Build() *ListScopedRolesRequest {
|
||||
@@ -311,8 +325,8 @@ func (b0 ListScopedRolesRequest_builder) Build() *ListScopedRolesRequest {
|
||||
x.PageSize = b.PageSize
|
||||
x.PageToken = b.PageToken
|
||||
x.ResourceScope = b.ResourceScope
|
||||
x.AssignableScope = b.AssignableScope
|
||||
x.NameFilter = b.NameFilter
|
||||
x.ScopeFilter = b.ScopeFilter
|
||||
return m0
|
||||
}
|
||||
|
||||
@@ -1092,11 +1106,6 @@ type ListScopedRoleAssignmentsRequest struct {
|
||||
PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
|
||||
// ResourceScope filters assignments by their resource scope if specified.
|
||||
ResourceScope *v1.Filter `protobuf:"bytes,3,opt,name=resource_scope,json=resourceScope,proto3" json:"resource_scope,omitempty"`
|
||||
// AssignedScope filters assignments by the scopes they assign to if specified (note: matches assignment
|
||||
// resources with 1 or more maching scopes, not all scopes within the assignment will necessarily match).
|
||||
AssignedScope *v1.Filter `protobuf:"bytes,4,opt,name=assigned_scope,json=assignedScope,proto3" json:"assigned_scope,omitempty"`
|
||||
// User optionally limits the list to assignments for a specific user.
|
||||
User string `protobuf:"bytes,5,opt,name=user,proto3" json:"user,omitempty"`
|
||||
// Role optionally limits the list to assignments for a specific role.
|
||||
@@ -1106,8 +1115,17 @@ type ListScopedRoleAssignmentsRequest struct {
|
||||
// is specifically used to support users discovering where they have been granted privileges across scopes,
|
||||
// and is not intended for general use.
|
||||
AllCallerAssignments bool `protobuf:"varint,7,opt,name=all_caller_assignments,json=allCallerAssignments,proto3" json:"all_caller_assignments,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter `protobuf:"bytes,8,opt,name=scope_filter,json=scopeFilter,proto3" json:"scope_filter,omitempty"`
|
||||
// AssignedScopeFilter filters assignments by the scopes they assign to if specified (note: matches
|
||||
// assignment resources with 1 or more matching scopes, not all scopes within the assignment will
|
||||
// necessarily match).
|
||||
AssignedScopeFilter *v1.Filter `protobuf:"bytes,9,opt,name=assigned_scope_filter,json=assignedScopeFilter,proto3" json:"assigned_scope_filter,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) Reset() {
|
||||
@@ -1149,20 +1167,6 @@ func (x *ListScopedRoleAssignmentsRequest) GetPageToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetResourceScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.ResourceScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetAssignedScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.AssignedScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetUser() string {
|
||||
if x != nil {
|
||||
return x.User
|
||||
@@ -1184,6 +1188,20 @@ func (x *ListScopedRoleAssignmentsRequest) GetAllCallerAssignments() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.ScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetAssignedScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.AssignedScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetPageSize(v int32) {
|
||||
x.PageSize = v
|
||||
}
|
||||
@@ -1192,14 +1210,6 @@ func (x *ListScopedRoleAssignmentsRequest) SetPageToken(v string) {
|
||||
x.PageToken = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetResourceScope(v *v1.Filter) {
|
||||
x.ResourceScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetAssignedScope(v *v1.Filter) {
|
||||
x.AssignedScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetUser(v string) {
|
||||
x.User = v
|
||||
}
|
||||
@@ -1212,26 +1222,34 @@ func (x *ListScopedRoleAssignmentsRequest) SetAllCallerAssignments(v bool) {
|
||||
x.AllCallerAssignments = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasResourceScope() bool {
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetScopeFilter(v *v1.Filter) {
|
||||
x.ScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetAssignedScopeFilter(v *v1.Filter) {
|
||||
x.AssignedScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.ResourceScope != nil
|
||||
return x.ScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasAssignedScope() bool {
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasAssignedScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.AssignedScope != nil
|
||||
return x.AssignedScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearResourceScope() {
|
||||
x.ResourceScope = nil
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearScopeFilter() {
|
||||
x.ScopeFilter = nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearAssignedScope() {
|
||||
x.AssignedScope = nil
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearAssignedScopeFilter() {
|
||||
x.AssignedScopeFilter = nil
|
||||
}
|
||||
|
||||
type ListScopedRoleAssignmentsRequest_builder struct {
|
||||
@@ -1241,11 +1259,6 @@ type ListScopedRoleAssignmentsRequest_builder struct {
|
||||
PageSize int32
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
PageToken string
|
||||
// ResourceScope filters assignments by their resource scope if specified.
|
||||
ResourceScope *v1.Filter
|
||||
// AssignedScope filters assignments by the scopes they assign to if specified (note: matches assignment
|
||||
// resources with 1 or more maching scopes, not all scopes within the assignment will necessarily match).
|
||||
AssignedScope *v1.Filter
|
||||
// User optionally limits the list to assignments for a specific user.
|
||||
User string
|
||||
// Role optionally limits the list to assignments for a specific role.
|
||||
@@ -1255,6 +1268,15 @@ type ListScopedRoleAssignmentsRequest_builder struct {
|
||||
// is specifically used to support users discovering where they have been granted privileges across scopes,
|
||||
// and is not intended for general use.
|
||||
AllCallerAssignments bool
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter
|
||||
// AssignedScopeFilter filters assignments by the scopes they assign to if specified (note: matches
|
||||
// assignment resources with 1 or more matching scopes, not all scopes within the assignment will
|
||||
// necessarily match).
|
||||
AssignedScopeFilter *v1.Filter
|
||||
}
|
||||
|
||||
func (b0 ListScopedRoleAssignmentsRequest_builder) Build() *ListScopedRoleAssignmentsRequest {
|
||||
@@ -1263,11 +1285,11 @@ func (b0 ListScopedRoleAssignmentsRequest_builder) Build() *ListScopedRoleAssign
|
||||
_, _ = b, x
|
||||
x.PageSize = b.PageSize
|
||||
x.PageToken = b.PageToken
|
||||
x.ResourceScope = b.ResourceScope
|
||||
x.AssignedScope = b.AssignedScope
|
||||
x.User = b.User
|
||||
x.Role = b.Role
|
||||
x.AllCallerAssignments = b.AllCallerAssignments
|
||||
x.ScopeFilter = b.ScopeFilter
|
||||
x.AssignedScopeFilter = b.AssignedScopeFilter
|
||||
return m0
|
||||
}
|
||||
|
||||
@@ -1917,15 +1939,15 @@ const file_teleport_scopes_access_v1_service_proto_rawDesc = "" +
|
||||
"\x14GetScopedRoleRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\"R\n" +
|
||||
"\x15GetScopedRoleResponse\x129\n" +
|
||||
"\x04role\x18\x01 \x01(\v2%.teleport.scopes.access.v1.ScopedRoleR\x04role\"\xff\x01\n" +
|
||||
"\x04role\x18\x01 \x01(\v2%.teleport.scopes.access.v1.ScopedRoleR\x04role\"\x93\x02\n" +
|
||||
"\x16ListScopedRolesRequest\x12\x1b\n" +
|
||||
"\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
|
||||
"\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12A\n" +
|
||||
"\x0eresource_scope\x18\x03 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rresourceScope\x12E\n" +
|
||||
"\x10assignable_scope\x18\x04 \x01(\v2\x1a.teleport.scopes.v1.FilterR\x0fassignableScope\x12\x1f\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12E\n" +
|
||||
"\x0eresource_scope\x18\x03 \x01(\v2\x1a.teleport.scopes.v1.FilterB\x02\x18\x01R\rresourceScope\x12\x1f\n" +
|
||||
"\vname_filter\x18\x05 \x01(\tR\n" +
|
||||
"nameFilter\"~\n" +
|
||||
"nameFilter\x12=\n" +
|
||||
"\fscope_filter\x18\x06 \x01(\v2\x1a.teleport.scopes.v1.FilterR\vscopeFilterJ\x04\b\x04\x10\x05R\x10assignable_scope\"~\n" +
|
||||
"\x17ListScopedRolesResponse\x12;\n" +
|
||||
"\x05roles\x18\x01 \x03(\v2%.teleport.scopes.access.v1.ScopedRoleR\x05roles\x12&\n" +
|
||||
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"T\n" +
|
||||
@@ -1951,16 +1973,16 @@ const file_teleport_scopes_access_v1_service_proto_rawDesc = "" +
|
||||
"\x1fGetScopedRoleAssignmentResponse\x12O\n" +
|
||||
"\n" +
|
||||
"assignment\x18\x01 \x01(\v2/.teleport.scopes.access.v1.ScopedRoleAssignmentR\n" +
|
||||
"assignment\"\xc2\x02\n" +
|
||||
"assignment\"\xf7\x02\n" +
|
||||
" ListScopedRoleAssignmentsRequest\x12\x1b\n" +
|
||||
"\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
|
||||
"\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12A\n" +
|
||||
"\x0eresource_scope\x18\x03 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rresourceScope\x12A\n" +
|
||||
"\x0eassigned_scope\x18\x04 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rassignedScope\x12\x12\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12\x12\n" +
|
||||
"\x04user\x18\x05 \x01(\tR\x04user\x12\x12\n" +
|
||||
"\x04role\x18\x06 \x01(\tR\x04role\x124\n" +
|
||||
"\x16all_caller_assignments\x18\a \x01(\bR\x14allCallerAssignments\"\x9e\x01\n" +
|
||||
"\x16all_caller_assignments\x18\a \x01(\bR\x14allCallerAssignments\x12=\n" +
|
||||
"\fscope_filter\x18\b \x01(\v2\x1a.teleport.scopes.v1.FilterR\vscopeFilter\x12N\n" +
|
||||
"\x15assigned_scope_filter\x18\t \x01(\v2\x1a.teleport.scopes.v1.FilterR\x13assignedScopeFilterJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\x0eresource_scopeR\x0eassigned_scope\"\x9e\x01\n" +
|
||||
"!ListScopedRoleAssignmentsResponse\x12Q\n" +
|
||||
"\vassignments\x18\x01 \x03(\v2/.teleport.scopes.access.v1.ScopedRoleAssignmentR\vassignments\x12&\n" +
|
||||
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\x8a\x01\n" +
|
||||
@@ -2040,7 +2062,7 @@ var file_teleport_scopes_access_v1_service_proto_goTypes = []any{
|
||||
var file_teleport_scopes_access_v1_service_proto_depIdxs = []int32{
|
||||
24, // 0: teleport.scopes.access.v1.GetScopedRoleResponse.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
25, // 1: teleport.scopes.access.v1.ListScopedRolesRequest.resource_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 2: teleport.scopes.access.v1.ListScopedRolesRequest.assignable_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 2: teleport.scopes.access.v1.ListScopedRolesRequest.scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
24, // 3: teleport.scopes.access.v1.ListScopedRolesResponse.roles:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
24, // 4: teleport.scopes.access.v1.CreateScopedRoleRequest.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
24, // 5: teleport.scopes.access.v1.CreateScopedRoleResponse.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
@@ -2049,8 +2071,8 @@ var file_teleport_scopes_access_v1_service_proto_depIdxs = []int32{
|
||||
24, // 8: teleport.scopes.access.v1.UpsertScopedRoleRequest.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
24, // 9: teleport.scopes.access.v1.UpsertScopedRoleResponse.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
26, // 10: teleport.scopes.access.v1.GetScopedRoleAssignmentResponse.assignment:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
25, // 11: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.resource_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 12: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.assigned_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 11: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 12: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.assigned_scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
26, // 13: teleport.scopes.access.v1.ListScopedRoleAssignmentsResponse.assignments:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
26, // 14: teleport.scopes.access.v1.CreateScopedRoleAssignmentRequest.assignment:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
26, // 15: teleport.scopes.access.v1.CreateScopedRoleAssignmentResponse.assignment:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
|
||||
@@ -168,14 +168,14 @@ func (b0 GetScopedRoleResponse_builder) Build() *GetScopedRoleResponse {
|
||||
|
||||
// ListScopedRolesRequest is the request to list scoped roles.
|
||||
type ListScopedRolesRequest struct {
|
||||
state protoimpl.MessageState `protogen:"opaque.v1"`
|
||||
xxx_hidden_PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3"`
|
||||
xxx_hidden_PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3"`
|
||||
xxx_hidden_ResourceScope *v1.Filter `protobuf:"bytes,3,opt,name=resource_scope,json=resourceScope,proto3"`
|
||||
xxx_hidden_AssignableScope *v1.Filter `protobuf:"bytes,4,opt,name=assignable_scope,json=assignableScope,proto3"`
|
||||
xxx_hidden_NameFilter string `protobuf:"bytes,5,opt,name=name_filter,json=nameFilter,proto3"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"opaque.v1"`
|
||||
xxx_hidden_PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3"`
|
||||
xxx_hidden_PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3"`
|
||||
xxx_hidden_ResourceScope *v1.Filter `protobuf:"bytes,3,opt,name=resource_scope,json=resourceScope,proto3"`
|
||||
xxx_hidden_NameFilter string `protobuf:"bytes,5,opt,name=name_filter,json=nameFilter,proto3"`
|
||||
xxx_hidden_ScopeFilter *v1.Filter `protobuf:"bytes,6,opt,name=scope_filter,json=scopeFilter,proto3"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) Reset() {
|
||||
@@ -217,6 +217,7 @@ func (x *ListScopedRolesRequest) GetPageToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) GetResourceScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_ResourceScope
|
||||
@@ -224,13 +225,6 @@ func (x *ListScopedRolesRequest) GetResourceScope() *v1.Filter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) GetAssignableScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_AssignableScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) GetNameFilter() string {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_NameFilter
|
||||
@@ -238,6 +232,13 @@ func (x *ListScopedRolesRequest) GetNameFilter() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) GetScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_ScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetPageSize(v int32) {
|
||||
x.xxx_hidden_PageSize = v
|
||||
}
|
||||
@@ -246,18 +247,20 @@ func (x *ListScopedRolesRequest) SetPageToken(v string) {
|
||||
x.xxx_hidden_PageToken = v
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) SetResourceScope(v *v1.Filter) {
|
||||
x.xxx_hidden_ResourceScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetAssignableScope(v *v1.Filter) {
|
||||
x.xxx_hidden_AssignableScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetNameFilter(v string) {
|
||||
x.xxx_hidden_NameFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) SetScopeFilter(v *v1.Filter) {
|
||||
x.xxx_hidden_ScopeFilter = v
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) HasResourceScope() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
@@ -265,19 +268,20 @@ func (x *ListScopedRolesRequest) HasResourceScope() bool {
|
||||
return x.xxx_hidden_ResourceScope != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) HasAssignableScope() bool {
|
||||
func (x *ListScopedRolesRequest) HasScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.xxx_hidden_AssignableScope != nil
|
||||
return x.xxx_hidden_ScopeFilter != nil
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
func (x *ListScopedRolesRequest) ClearResourceScope() {
|
||||
x.xxx_hidden_ResourceScope = nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRolesRequest) ClearAssignableScope() {
|
||||
x.xxx_hidden_AssignableScope = nil
|
||||
func (x *ListScopedRolesRequest) ClearScopeFilter() {
|
||||
x.xxx_hidden_ScopeFilter = nil
|
||||
}
|
||||
|
||||
type ListScopedRolesRequest_builder struct {
|
||||
@@ -288,12 +292,17 @@ type ListScopedRolesRequest_builder struct {
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
PageToken string
|
||||
// ResourceScope filters roles by their resource scope if specified.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/access/v1/service.proto.
|
||||
ResourceScope *v1.Filter
|
||||
// AssignableScope filters roles by their assignable scope if specified.
|
||||
AssignableScope *v1.Filter
|
||||
// NameFilter filters roles by their name using a case-insensitive substring
|
||||
// match if specified.
|
||||
NameFilter string
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter
|
||||
}
|
||||
|
||||
func (b0 ListScopedRolesRequest_builder) Build() *ListScopedRolesRequest {
|
||||
@@ -303,8 +312,8 @@ func (b0 ListScopedRolesRequest_builder) Build() *ListScopedRolesRequest {
|
||||
x.xxx_hidden_PageSize = b.PageSize
|
||||
x.xxx_hidden_PageToken = b.PageToken
|
||||
x.xxx_hidden_ResourceScope = b.ResourceScope
|
||||
x.xxx_hidden_AssignableScope = b.AssignableScope
|
||||
x.xxx_hidden_NameFilter = b.NameFilter
|
||||
x.xxx_hidden_ScopeFilter = b.ScopeFilter
|
||||
return m0
|
||||
}
|
||||
|
||||
@@ -1071,11 +1080,11 @@ type ListScopedRoleAssignmentsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"opaque.v1"`
|
||||
xxx_hidden_PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3"`
|
||||
xxx_hidden_PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3"`
|
||||
xxx_hidden_ResourceScope *v1.Filter `protobuf:"bytes,3,opt,name=resource_scope,json=resourceScope,proto3"`
|
||||
xxx_hidden_AssignedScope *v1.Filter `protobuf:"bytes,4,opt,name=assigned_scope,json=assignedScope,proto3"`
|
||||
xxx_hidden_User string `protobuf:"bytes,5,opt,name=user,proto3"`
|
||||
xxx_hidden_Role string `protobuf:"bytes,6,opt,name=role,proto3"`
|
||||
xxx_hidden_AllCallerAssignments bool `protobuf:"varint,7,opt,name=all_caller_assignments,json=allCallerAssignments,proto3"`
|
||||
xxx_hidden_ScopeFilter *v1.Filter `protobuf:"bytes,8,opt,name=scope_filter,json=scopeFilter,proto3"`
|
||||
xxx_hidden_AssignedScopeFilter *v1.Filter `protobuf:"bytes,9,opt,name=assigned_scope_filter,json=assignedScopeFilter,proto3"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -1119,20 +1128,6 @@ func (x *ListScopedRoleAssignmentsRequest) GetPageToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetResourceScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_ResourceScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetAssignedScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_AssignedScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetUser() string {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_User
|
||||
@@ -1154,6 +1149,20 @@ func (x *ListScopedRoleAssignmentsRequest) GetAllCallerAssignments() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_ScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) GetAssignedScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_AssignedScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetPageSize(v int32) {
|
||||
x.xxx_hidden_PageSize = v
|
||||
}
|
||||
@@ -1162,14 +1171,6 @@ func (x *ListScopedRoleAssignmentsRequest) SetPageToken(v string) {
|
||||
x.xxx_hidden_PageToken = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetResourceScope(v *v1.Filter) {
|
||||
x.xxx_hidden_ResourceScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetAssignedScope(v *v1.Filter) {
|
||||
x.xxx_hidden_AssignedScope = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetUser(v string) {
|
||||
x.xxx_hidden_User = v
|
||||
}
|
||||
@@ -1182,26 +1183,34 @@ func (x *ListScopedRoleAssignmentsRequest) SetAllCallerAssignments(v bool) {
|
||||
x.xxx_hidden_AllCallerAssignments = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasResourceScope() bool {
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetScopeFilter(v *v1.Filter) {
|
||||
x.xxx_hidden_ScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) SetAssignedScopeFilter(v *v1.Filter) {
|
||||
x.xxx_hidden_AssignedScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.xxx_hidden_ResourceScope != nil
|
||||
return x.xxx_hidden_ScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasAssignedScope() bool {
|
||||
func (x *ListScopedRoleAssignmentsRequest) HasAssignedScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.xxx_hidden_AssignedScope != nil
|
||||
return x.xxx_hidden_AssignedScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearResourceScope() {
|
||||
x.xxx_hidden_ResourceScope = nil
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearScopeFilter() {
|
||||
x.xxx_hidden_ScopeFilter = nil
|
||||
}
|
||||
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearAssignedScope() {
|
||||
x.xxx_hidden_AssignedScope = nil
|
||||
func (x *ListScopedRoleAssignmentsRequest) ClearAssignedScopeFilter() {
|
||||
x.xxx_hidden_AssignedScopeFilter = nil
|
||||
}
|
||||
|
||||
type ListScopedRoleAssignmentsRequest_builder struct {
|
||||
@@ -1211,11 +1220,6 @@ type ListScopedRoleAssignmentsRequest_builder struct {
|
||||
PageSize int32
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
PageToken string
|
||||
// ResourceScope filters assignments by their resource scope if specified.
|
||||
ResourceScope *v1.Filter
|
||||
// AssignedScope filters assignments by the scopes they assign to if specified (note: matches assignment
|
||||
// resources with 1 or more maching scopes, not all scopes within the assignment will necessarily match).
|
||||
AssignedScope *v1.Filter
|
||||
// User optionally limits the list to assignments for a specific user.
|
||||
User string
|
||||
// Role optionally limits the list to assignments for a specific role.
|
||||
@@ -1225,6 +1229,15 @@ type ListScopedRoleAssignmentsRequest_builder struct {
|
||||
// is specifically used to support users discovering where they have been granted privileges across scopes,
|
||||
// and is not intended for general use.
|
||||
AllCallerAssignments bool
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter
|
||||
// AssignedScopeFilter filters assignments by the scopes they assign to if specified (note: matches
|
||||
// assignment resources with 1 or more matching scopes, not all scopes within the assignment will
|
||||
// necessarily match).
|
||||
AssignedScopeFilter *v1.Filter
|
||||
}
|
||||
|
||||
func (b0 ListScopedRoleAssignmentsRequest_builder) Build() *ListScopedRoleAssignmentsRequest {
|
||||
@@ -1233,11 +1246,11 @@ func (b0 ListScopedRoleAssignmentsRequest_builder) Build() *ListScopedRoleAssign
|
||||
_, _ = b, x
|
||||
x.xxx_hidden_PageSize = b.PageSize
|
||||
x.xxx_hidden_PageToken = b.PageToken
|
||||
x.xxx_hidden_ResourceScope = b.ResourceScope
|
||||
x.xxx_hidden_AssignedScope = b.AssignedScope
|
||||
x.xxx_hidden_User = b.User
|
||||
x.xxx_hidden_Role = b.Role
|
||||
x.xxx_hidden_AllCallerAssignments = b.AllCallerAssignments
|
||||
x.xxx_hidden_ScopeFilter = b.ScopeFilter
|
||||
x.xxx_hidden_AssignedScopeFilter = b.AssignedScopeFilter
|
||||
return m0
|
||||
}
|
||||
|
||||
@@ -1878,15 +1891,15 @@ const file_teleport_scopes_access_v1_service_proto_rawDesc = "" +
|
||||
"\x14GetScopedRoleRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\"R\n" +
|
||||
"\x15GetScopedRoleResponse\x129\n" +
|
||||
"\x04role\x18\x01 \x01(\v2%.teleport.scopes.access.v1.ScopedRoleR\x04role\"\xff\x01\n" +
|
||||
"\x04role\x18\x01 \x01(\v2%.teleport.scopes.access.v1.ScopedRoleR\x04role\"\x93\x02\n" +
|
||||
"\x16ListScopedRolesRequest\x12\x1b\n" +
|
||||
"\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
|
||||
"\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12A\n" +
|
||||
"\x0eresource_scope\x18\x03 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rresourceScope\x12E\n" +
|
||||
"\x10assignable_scope\x18\x04 \x01(\v2\x1a.teleport.scopes.v1.FilterR\x0fassignableScope\x12\x1f\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12E\n" +
|
||||
"\x0eresource_scope\x18\x03 \x01(\v2\x1a.teleport.scopes.v1.FilterB\x02\x18\x01R\rresourceScope\x12\x1f\n" +
|
||||
"\vname_filter\x18\x05 \x01(\tR\n" +
|
||||
"nameFilter\"~\n" +
|
||||
"nameFilter\x12=\n" +
|
||||
"\fscope_filter\x18\x06 \x01(\v2\x1a.teleport.scopes.v1.FilterR\vscopeFilterJ\x04\b\x04\x10\x05R\x10assignable_scope\"~\n" +
|
||||
"\x17ListScopedRolesResponse\x12;\n" +
|
||||
"\x05roles\x18\x01 \x03(\v2%.teleport.scopes.access.v1.ScopedRoleR\x05roles\x12&\n" +
|
||||
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"T\n" +
|
||||
@@ -1912,16 +1925,16 @@ const file_teleport_scopes_access_v1_service_proto_rawDesc = "" +
|
||||
"\x1fGetScopedRoleAssignmentResponse\x12O\n" +
|
||||
"\n" +
|
||||
"assignment\x18\x01 \x01(\v2/.teleport.scopes.access.v1.ScopedRoleAssignmentR\n" +
|
||||
"assignment\"\xc2\x02\n" +
|
||||
"assignment\"\xf7\x02\n" +
|
||||
" ListScopedRoleAssignmentsRequest\x12\x1b\n" +
|
||||
"\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
|
||||
"\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12A\n" +
|
||||
"\x0eresource_scope\x18\x03 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rresourceScope\x12A\n" +
|
||||
"\x0eassigned_scope\x18\x04 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rassignedScope\x12\x12\n" +
|
||||
"page_token\x18\x02 \x01(\tR\tpageToken\x12\x12\n" +
|
||||
"\x04user\x18\x05 \x01(\tR\x04user\x12\x12\n" +
|
||||
"\x04role\x18\x06 \x01(\tR\x04role\x124\n" +
|
||||
"\x16all_caller_assignments\x18\a \x01(\bR\x14allCallerAssignments\"\x9e\x01\n" +
|
||||
"\x16all_caller_assignments\x18\a \x01(\bR\x14allCallerAssignments\x12=\n" +
|
||||
"\fscope_filter\x18\b \x01(\v2\x1a.teleport.scopes.v1.FilterR\vscopeFilter\x12N\n" +
|
||||
"\x15assigned_scope_filter\x18\t \x01(\v2\x1a.teleport.scopes.v1.FilterR\x13assignedScopeFilterJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05R\x0eresource_scopeR\x0eassigned_scope\"\x9e\x01\n" +
|
||||
"!ListScopedRoleAssignmentsResponse\x12Q\n" +
|
||||
"\vassignments\x18\x01 \x03(\v2/.teleport.scopes.access.v1.ScopedRoleAssignmentR\vassignments\x12&\n" +
|
||||
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\x8a\x01\n" +
|
||||
@@ -2001,7 +2014,7 @@ var file_teleport_scopes_access_v1_service_proto_goTypes = []any{
|
||||
var file_teleport_scopes_access_v1_service_proto_depIdxs = []int32{
|
||||
24, // 0: teleport.scopes.access.v1.GetScopedRoleResponse.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
25, // 1: teleport.scopes.access.v1.ListScopedRolesRequest.resource_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 2: teleport.scopes.access.v1.ListScopedRolesRequest.assignable_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 2: teleport.scopes.access.v1.ListScopedRolesRequest.scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
24, // 3: teleport.scopes.access.v1.ListScopedRolesResponse.roles:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
24, // 4: teleport.scopes.access.v1.CreateScopedRoleRequest.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
24, // 5: teleport.scopes.access.v1.CreateScopedRoleResponse.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
@@ -2010,8 +2023,8 @@ var file_teleport_scopes_access_v1_service_proto_depIdxs = []int32{
|
||||
24, // 8: teleport.scopes.access.v1.UpsertScopedRoleRequest.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
24, // 9: teleport.scopes.access.v1.UpsertScopedRoleResponse.role:type_name -> teleport.scopes.access.v1.ScopedRole
|
||||
26, // 10: teleport.scopes.access.v1.GetScopedRoleAssignmentResponse.assignment:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
25, // 11: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.resource_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 12: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.assigned_scope:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 11: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
25, // 12: teleport.scopes.access.v1.ListScopedRoleAssignmentsRequest.assigned_scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
26, // 13: teleport.scopes.access.v1.ListScopedRoleAssignmentsResponse.assignments:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
26, // 14: teleport.scopes.access.v1.CreateScopedRoleAssignmentRequest.assignment:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
26, // 15: teleport.scopes.access.v1.CreateScopedRoleAssignmentResponse.assignment:type_name -> teleport.scopes.access.v1.ScopedRoleAssignment
|
||||
|
||||
@@ -203,10 +203,6 @@ func (b0 GetScopedTokenResponse_builder) Build() *GetScopedTokenResponse {
|
||||
// ListScopedTokensRequest is the request to list scoped tokens.
|
||||
type ListScopedTokensRequest struct {
|
||||
state protoimpl.MessageState `protogen:"hybrid.v1"`
|
||||
// Filter tokens by their resource scope.
|
||||
ResourceScope *v1.Filter `protobuf:"bytes,1,opt,name=resource_scope,json=resourceScope,proto3" json:"resource_scope,omitempty"`
|
||||
// Filter tokens by their assigned scope.
|
||||
AssignedScope *v1.Filter `protobuf:"bytes,2,opt,name=assigned_scope,json=assignedScope,proto3" json:"assigned_scope,omitempty"`
|
||||
// The pagination cursor.
|
||||
Cursor string `protobuf:"bytes,3,opt,name=cursor,proto3" json:"cursor,omitempty"`
|
||||
// The maximum number of results to return.
|
||||
@@ -216,9 +212,16 @@ type ListScopedTokensRequest struct {
|
||||
// Filter tokens that match all provided labels.
|
||||
Labels map[string]string `protobuf:"bytes,6,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
// If true, include the token secrets in the response.
|
||||
WithSecrets bool `protobuf:"varint,7,opt,name=with_secrets,json=withSecrets,proto3" json:"with_secrets,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
WithSecrets bool `protobuf:"varint,7,opt,name=with_secrets,json=withSecrets,proto3" json:"with_secrets,omitempty"`
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter `protobuf:"bytes,8,opt,name=scope_filter,json=scopeFilter,proto3" json:"scope_filter,omitempty"`
|
||||
// AssignedScopeFilter filters tokens by their assigned scope if specified.
|
||||
AssignedScopeFilter *v1.Filter `protobuf:"bytes,9,opt,name=assigned_scope_filter,json=assignedScopeFilter,proto3" json:"assigned_scope_filter,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) Reset() {
|
||||
@@ -246,20 +249,6 @@ func (x *ListScopedTokensRequest) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) GetResourceScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.ResourceScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) GetAssignedScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.AssignedScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) GetCursor() string {
|
||||
if x != nil {
|
||||
return x.Cursor
|
||||
@@ -295,12 +284,18 @@ func (x *ListScopedTokensRequest) GetWithSecrets() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetResourceScope(v *v1.Filter) {
|
||||
x.ResourceScope = v
|
||||
func (x *ListScopedTokensRequest) GetScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.ScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetAssignedScope(v *v1.Filter) {
|
||||
x.AssignedScope = v
|
||||
func (x *ListScopedTokensRequest) GetAssignedScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.AssignedScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetCursor(v string) {
|
||||
@@ -323,35 +318,39 @@ func (x *ListScopedTokensRequest) SetWithSecrets(v bool) {
|
||||
x.WithSecrets = v
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) HasResourceScope() bool {
|
||||
func (x *ListScopedTokensRequest) SetScopeFilter(v *v1.Filter) {
|
||||
x.ScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetAssignedScopeFilter(v *v1.Filter) {
|
||||
x.AssignedScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) HasScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.ResourceScope != nil
|
||||
return x.ScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) HasAssignedScope() bool {
|
||||
func (x *ListScopedTokensRequest) HasAssignedScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.AssignedScope != nil
|
||||
return x.AssignedScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) ClearResourceScope() {
|
||||
x.ResourceScope = nil
|
||||
func (x *ListScopedTokensRequest) ClearScopeFilter() {
|
||||
x.ScopeFilter = nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) ClearAssignedScope() {
|
||||
x.AssignedScope = nil
|
||||
func (x *ListScopedTokensRequest) ClearAssignedScopeFilter() {
|
||||
x.AssignedScopeFilter = nil
|
||||
}
|
||||
|
||||
type ListScopedTokensRequest_builder struct {
|
||||
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
|
||||
|
||||
// Filter tokens by their resource scope.
|
||||
ResourceScope *v1.Filter
|
||||
// Filter tokens by their assigned scope.
|
||||
AssignedScope *v1.Filter
|
||||
// The pagination cursor.
|
||||
Cursor string
|
||||
// The maximum number of results to return.
|
||||
@@ -362,19 +361,26 @@ type ListScopedTokensRequest_builder struct {
|
||||
Labels map[string]string
|
||||
// If true, include the token secrets in the response.
|
||||
WithSecrets bool
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter
|
||||
// AssignedScopeFilter filters tokens by their assigned scope if specified.
|
||||
AssignedScopeFilter *v1.Filter
|
||||
}
|
||||
|
||||
func (b0 ListScopedTokensRequest_builder) Build() *ListScopedTokensRequest {
|
||||
m0 := &ListScopedTokensRequest{}
|
||||
b, x := &b0, m0
|
||||
_, _ = b, x
|
||||
x.ResourceScope = b.ResourceScope
|
||||
x.AssignedScope = b.AssignedScope
|
||||
x.Cursor = b.Cursor
|
||||
x.Limit = b.Limit
|
||||
x.Roles = b.Roles
|
||||
x.Labels = b.Labels
|
||||
x.WithSecrets = b.WithSecrets
|
||||
x.ScopeFilter = b.ScopeFilter
|
||||
x.AssignedScopeFilter = b.AssignedScopeFilter
|
||||
return m0
|
||||
}
|
||||
|
||||
@@ -1027,18 +1033,18 @@ const file_teleport_scopes_joining_v1_service_proto_rawDesc = "" +
|
||||
"withSecret\x12\x14\n" +
|
||||
"\x05scope\x18\x03 \x01(\tR\x05scope\"W\n" +
|
||||
"\x16GetScopedTokenResponse\x12=\n" +
|
||||
"\x05token\x18\x01 \x01(\v2'.teleport.scopes.joining.v1.ScopedTokenR\x05token\"\x9a\x03\n" +
|
||||
"\x17ListScopedTokensRequest\x12A\n" +
|
||||
"\x0eresource_scope\x18\x01 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rresourceScope\x12A\n" +
|
||||
"\x0eassigned_scope\x18\x02 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rassignedScope\x12\x16\n" +
|
||||
"\x05token\x18\x01 \x01(\v2'.teleport.scopes.joining.v1.ScopedTokenR\x05token\"\xcf\x03\n" +
|
||||
"\x17ListScopedTokensRequest\x12\x16\n" +
|
||||
"\x06cursor\x18\x03 \x01(\tR\x06cursor\x12\x14\n" +
|
||||
"\x05limit\x18\x04 \x01(\rR\x05limit\x12\x14\n" +
|
||||
"\x05roles\x18\x05 \x03(\tR\x05roles\x12W\n" +
|
||||
"\x06labels\x18\x06 \x03(\v2?.teleport.scopes.joining.v1.ListScopedTokensRequest.LabelsEntryR\x06labels\x12!\n" +
|
||||
"\fwith_secrets\x18\a \x01(\bR\vwithSecrets\x1a9\n" +
|
||||
"\fwith_secrets\x18\a \x01(\bR\vwithSecrets\x12=\n" +
|
||||
"\fscope_filter\x18\b \x01(\v2\x1a.teleport.scopes.v1.FilterR\vscopeFilter\x12N\n" +
|
||||
"\x15assigned_scope_filter\x18\t \x01(\v2\x1a.teleport.scopes.v1.FilterR\x13assignedScopeFilter\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"s\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\x02\x10\x03R\x0eresource_scopeR\x0eassigned_scope\"s\n" +
|
||||
"\x18ListScopedTokensResponse\x12?\n" +
|
||||
"\x06tokens\x18\x01 \x03(\v2'.teleport.scopes.joining.v1.ScopedTokenR\x06tokens\x12\x16\n" +
|
||||
"\x06cursor\x18\x02 \x01(\tR\x06cursor\"Y\n" +
|
||||
@@ -1087,9 +1093,9 @@ var file_teleport_scopes_joining_v1_service_proto_goTypes = []any{
|
||||
}
|
||||
var file_teleport_scopes_joining_v1_service_proto_depIdxs = []int32{
|
||||
13, // 0: teleport.scopes.joining.v1.GetScopedTokenResponse.token:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
14, // 1: teleport.scopes.joining.v1.ListScopedTokensRequest.resource_scope:type_name -> teleport.scopes.v1.Filter
|
||||
14, // 2: teleport.scopes.joining.v1.ListScopedTokensRequest.assigned_scope:type_name -> teleport.scopes.v1.Filter
|
||||
12, // 3: teleport.scopes.joining.v1.ListScopedTokensRequest.labels:type_name -> teleport.scopes.joining.v1.ListScopedTokensRequest.LabelsEntry
|
||||
12, // 1: teleport.scopes.joining.v1.ListScopedTokensRequest.labels:type_name -> teleport.scopes.joining.v1.ListScopedTokensRequest.LabelsEntry
|
||||
14, // 2: teleport.scopes.joining.v1.ListScopedTokensRequest.scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
14, // 3: teleport.scopes.joining.v1.ListScopedTokensRequest.assigned_scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
13, // 4: teleport.scopes.joining.v1.ListScopedTokensResponse.tokens:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
13, // 5: teleport.scopes.joining.v1.CreateScopedTokenRequest.token:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
13, // 6: teleport.scopes.joining.v1.CreateScopedTokenResponse.token:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
|
||||
@@ -198,16 +198,16 @@ func (b0 GetScopedTokenResponse_builder) Build() *GetScopedTokenResponse {
|
||||
|
||||
// ListScopedTokensRequest is the request to list scoped tokens.
|
||||
type ListScopedTokensRequest struct {
|
||||
state protoimpl.MessageState `protogen:"opaque.v1"`
|
||||
xxx_hidden_ResourceScope *v1.Filter `protobuf:"bytes,1,opt,name=resource_scope,json=resourceScope,proto3"`
|
||||
xxx_hidden_AssignedScope *v1.Filter `protobuf:"bytes,2,opt,name=assigned_scope,json=assignedScope,proto3"`
|
||||
xxx_hidden_Cursor string `protobuf:"bytes,3,opt,name=cursor,proto3"`
|
||||
xxx_hidden_Limit uint32 `protobuf:"varint,4,opt,name=limit,proto3"`
|
||||
xxx_hidden_Roles []string `protobuf:"bytes,5,rep,name=roles,proto3"`
|
||||
xxx_hidden_Labels map[string]string `protobuf:"bytes,6,rep,name=labels,proto3" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
xxx_hidden_WithSecrets bool `protobuf:"varint,7,opt,name=with_secrets,json=withSecrets,proto3"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"opaque.v1"`
|
||||
xxx_hidden_Cursor string `protobuf:"bytes,3,opt,name=cursor,proto3"`
|
||||
xxx_hidden_Limit uint32 `protobuf:"varint,4,opt,name=limit,proto3"`
|
||||
xxx_hidden_Roles []string `protobuf:"bytes,5,rep,name=roles,proto3"`
|
||||
xxx_hidden_Labels map[string]string `protobuf:"bytes,6,rep,name=labels,proto3" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
xxx_hidden_WithSecrets bool `protobuf:"varint,7,opt,name=with_secrets,json=withSecrets,proto3"`
|
||||
xxx_hidden_ScopeFilter *v1.Filter `protobuf:"bytes,8,opt,name=scope_filter,json=scopeFilter,proto3"`
|
||||
xxx_hidden_AssignedScopeFilter *v1.Filter `protobuf:"bytes,9,opt,name=assigned_scope_filter,json=assignedScopeFilter,proto3"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) Reset() {
|
||||
@@ -235,20 +235,6 @@ func (x *ListScopedTokensRequest) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) GetResourceScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_ResourceScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) GetAssignedScope() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_AssignedScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) GetCursor() string {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_Cursor
|
||||
@@ -284,12 +270,18 @@ func (x *ListScopedTokensRequest) GetWithSecrets() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetResourceScope(v *v1.Filter) {
|
||||
x.xxx_hidden_ResourceScope = v
|
||||
func (x *ListScopedTokensRequest) GetScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_ScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetAssignedScope(v *v1.Filter) {
|
||||
x.xxx_hidden_AssignedScope = v
|
||||
func (x *ListScopedTokensRequest) GetAssignedScopeFilter() *v1.Filter {
|
||||
if x != nil {
|
||||
return x.xxx_hidden_AssignedScopeFilter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetCursor(v string) {
|
||||
@@ -312,35 +304,39 @@ func (x *ListScopedTokensRequest) SetWithSecrets(v bool) {
|
||||
x.xxx_hidden_WithSecrets = v
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) HasResourceScope() bool {
|
||||
func (x *ListScopedTokensRequest) SetScopeFilter(v *v1.Filter) {
|
||||
x.xxx_hidden_ScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) SetAssignedScopeFilter(v *v1.Filter) {
|
||||
x.xxx_hidden_AssignedScopeFilter = v
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) HasScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.xxx_hidden_ResourceScope != nil
|
||||
return x.xxx_hidden_ScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) HasAssignedScope() bool {
|
||||
func (x *ListScopedTokensRequest) HasAssignedScopeFilter() bool {
|
||||
if x == nil {
|
||||
return false
|
||||
}
|
||||
return x.xxx_hidden_AssignedScope != nil
|
||||
return x.xxx_hidden_AssignedScopeFilter != nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) ClearResourceScope() {
|
||||
x.xxx_hidden_ResourceScope = nil
|
||||
func (x *ListScopedTokensRequest) ClearScopeFilter() {
|
||||
x.xxx_hidden_ScopeFilter = nil
|
||||
}
|
||||
|
||||
func (x *ListScopedTokensRequest) ClearAssignedScope() {
|
||||
x.xxx_hidden_AssignedScope = nil
|
||||
func (x *ListScopedTokensRequest) ClearAssignedScopeFilter() {
|
||||
x.xxx_hidden_AssignedScopeFilter = nil
|
||||
}
|
||||
|
||||
type ListScopedTokensRequest_builder struct {
|
||||
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
|
||||
|
||||
// Filter tokens by their resource scope.
|
||||
ResourceScope *v1.Filter
|
||||
// Filter tokens by their assigned scope.
|
||||
AssignedScope *v1.Filter
|
||||
// The pagination cursor.
|
||||
Cursor string
|
||||
// The maximum number of results to return.
|
||||
@@ -351,19 +347,26 @@ type ListScopedTokensRequest_builder struct {
|
||||
Labels map[string]string
|
||||
// If true, include the token secrets in the response.
|
||||
WithSecrets bool
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
ScopeFilter *v1.Filter
|
||||
// AssignedScopeFilter filters tokens by their assigned scope if specified.
|
||||
AssignedScopeFilter *v1.Filter
|
||||
}
|
||||
|
||||
func (b0 ListScopedTokensRequest_builder) Build() *ListScopedTokensRequest {
|
||||
m0 := &ListScopedTokensRequest{}
|
||||
b, x := &b0, m0
|
||||
_, _ = b, x
|
||||
x.xxx_hidden_ResourceScope = b.ResourceScope
|
||||
x.xxx_hidden_AssignedScope = b.AssignedScope
|
||||
x.xxx_hidden_Cursor = b.Cursor
|
||||
x.xxx_hidden_Limit = b.Limit
|
||||
x.xxx_hidden_Roles = b.Roles
|
||||
x.xxx_hidden_Labels = b.Labels
|
||||
x.xxx_hidden_WithSecrets = b.WithSecrets
|
||||
x.xxx_hidden_ScopeFilter = b.ScopeFilter
|
||||
x.xxx_hidden_AssignedScopeFilter = b.AssignedScopeFilter
|
||||
return m0
|
||||
}
|
||||
|
||||
@@ -1007,18 +1010,18 @@ const file_teleport_scopes_joining_v1_service_proto_rawDesc = "" +
|
||||
"withSecret\x12\x14\n" +
|
||||
"\x05scope\x18\x03 \x01(\tR\x05scope\"W\n" +
|
||||
"\x16GetScopedTokenResponse\x12=\n" +
|
||||
"\x05token\x18\x01 \x01(\v2'.teleport.scopes.joining.v1.ScopedTokenR\x05token\"\x9a\x03\n" +
|
||||
"\x17ListScopedTokensRequest\x12A\n" +
|
||||
"\x0eresource_scope\x18\x01 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rresourceScope\x12A\n" +
|
||||
"\x0eassigned_scope\x18\x02 \x01(\v2\x1a.teleport.scopes.v1.FilterR\rassignedScope\x12\x16\n" +
|
||||
"\x05token\x18\x01 \x01(\v2'.teleport.scopes.joining.v1.ScopedTokenR\x05token\"\xcf\x03\n" +
|
||||
"\x17ListScopedTokensRequest\x12\x16\n" +
|
||||
"\x06cursor\x18\x03 \x01(\tR\x06cursor\x12\x14\n" +
|
||||
"\x05limit\x18\x04 \x01(\rR\x05limit\x12\x14\n" +
|
||||
"\x05roles\x18\x05 \x03(\tR\x05roles\x12W\n" +
|
||||
"\x06labels\x18\x06 \x03(\v2?.teleport.scopes.joining.v1.ListScopedTokensRequest.LabelsEntryR\x06labels\x12!\n" +
|
||||
"\fwith_secrets\x18\a \x01(\bR\vwithSecrets\x1a9\n" +
|
||||
"\fwith_secrets\x18\a \x01(\bR\vwithSecrets\x12=\n" +
|
||||
"\fscope_filter\x18\b \x01(\v2\x1a.teleport.scopes.v1.FilterR\vscopeFilter\x12N\n" +
|
||||
"\x15assigned_scope_filter\x18\t \x01(\v2\x1a.teleport.scopes.v1.FilterR\x13assignedScopeFilter\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"s\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\x02\x10\x03R\x0eresource_scopeR\x0eassigned_scope\"s\n" +
|
||||
"\x18ListScopedTokensResponse\x12?\n" +
|
||||
"\x06tokens\x18\x01 \x03(\v2'.teleport.scopes.joining.v1.ScopedTokenR\x06tokens\x12\x16\n" +
|
||||
"\x06cursor\x18\x02 \x01(\tR\x06cursor\"Y\n" +
|
||||
@@ -1067,9 +1070,9 @@ var file_teleport_scopes_joining_v1_service_proto_goTypes = []any{
|
||||
}
|
||||
var file_teleport_scopes_joining_v1_service_proto_depIdxs = []int32{
|
||||
13, // 0: teleport.scopes.joining.v1.GetScopedTokenResponse.token:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
14, // 1: teleport.scopes.joining.v1.ListScopedTokensRequest.resource_scope:type_name -> teleport.scopes.v1.Filter
|
||||
14, // 2: teleport.scopes.joining.v1.ListScopedTokensRequest.assigned_scope:type_name -> teleport.scopes.v1.Filter
|
||||
12, // 3: teleport.scopes.joining.v1.ListScopedTokensRequest.labels:type_name -> teleport.scopes.joining.v1.ListScopedTokensRequest.LabelsEntry
|
||||
12, // 1: teleport.scopes.joining.v1.ListScopedTokensRequest.labels:type_name -> teleport.scopes.joining.v1.ListScopedTokensRequest.LabelsEntry
|
||||
14, // 2: teleport.scopes.joining.v1.ListScopedTokensRequest.scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
14, // 3: teleport.scopes.joining.v1.ListScopedTokensRequest.assigned_scope_filter:type_name -> teleport.scopes.v1.Filter
|
||||
13, // 4: teleport.scopes.joining.v1.ListScopedTokensResponse.tokens:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
13, // 5: teleport.scopes.joining.v1.CreateScopedTokenRequest.token:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
13, // 6: teleport.scopes.joining.v1.CreateScopedTokenResponse.token:type_name -> teleport.scopes.joining.v1.ScopedToken
|
||||
|
||||
@@ -86,37 +86,79 @@ func (x PinKind) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Mode determines the mode of scoping when a query specifies a scope. When a query specifies a scope,
|
||||
// one of two questions is typically trying to be answered. Either, what resources are "in" and/or "subject to"
|
||||
// a given scope, or what policies are "applicable to" a given scope.
|
||||
// Mode determines how a Filter matches resource scopes relative to the scope specified by the
|
||||
// filter. Most modes select resources by their relationship to Filter.Scope (exact, descendants,
|
||||
// ancestors, or all relatives). Two special modes (MODE_UNSCOPED and MODE_ALL) do not select by
|
||||
// relationship and instead match the unscoped subset or everything the caller may see, respectively.
|
||||
//
|
||||
// NOTE: Our internals treat an unspecified filter and a MODE_ALL filter is both being "match all",
|
||||
// *however* the API/authz layer treats them differently. A MODE_ALL filter is interpreted as being
|
||||
// intentional. However, an omitted filter is replaced by an identity-dependent default (MODE_EXACT
|
||||
// for scoped callers and MODE_UNSCOPED for unscoped callers). This conservative defaulting is a key
|
||||
// component of the backwards comatibility and safety story for scope-enabled APIs. Any callsite being
|
||||
// updated to use an inclusive filtering mode like MODE_ALL must be carefully validated to ensure that
|
||||
// it is safe in the context of issues like the "confused deputy" problem.
|
||||
type Mode int32
|
||||
|
||||
const (
|
||||
// MODE_UNSPECIFIED indicates that no scope-based filtering has been specified.
|
||||
// MODE_UNSPECIFIED indicates that no scope-based filtering has been specified. Cache/backend impls
|
||||
// treat this as equivalent to MODE_ALL. The API/authz layer replaces this with one of MODE_EXACT or
|
||||
// MODE_UNSCOPED depending on whether the caller is scoped or unscoped, respectively.
|
||||
Mode_MODE_UNSPECIFIED Mode = 0
|
||||
// MODE_RESOURCES_SUBJECT_TO_SCOPE matches scopes by the resource subjugation rules. In the
|
||||
// terminology of scope comparison, this means Equivalent and Descendant scopes. This is the mode that most
|
||||
// user-facing scoped queries should use, as it intuitively answers the question of "what is the contents of
|
||||
// scope X?". See the 'lib/scopes' package for more detailed discussion of scope comparison/heirarchy.
|
||||
Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE Mode = 1
|
||||
// MODE_POLICIES_APPLICABLE_TO_SCOPE matches scopes by the policy application rules. In the
|
||||
// terminology of scope comparison, this means Ancestor and Equivalent scopes. This is the mode that most caching
|
||||
// and access-control related queries should use as it answers the question "what policies might affect access to
|
||||
// this resource at scope X?". See the 'lib/scopes' package for more detailed discussion of scope comparison/heirarchy.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/v1/scopes.proto.
|
||||
Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE Mode = 2
|
||||
// MODE_EXACT matches resources in the exact scope specified by Filter.Scope (Equivalent only). This is
|
||||
// the minimal/safe default for watch/list requests initiated by scoped callers that do not specify an
|
||||
// explicit filter.
|
||||
Mode_MODE_EXACT Mode = 3
|
||||
// MODE_DESCENDANTS matches the scope specified by Filter.Scope and all of its descendants. For example,
|
||||
// a Filter.Scope of `/staging/west` would match `/staging/west` and `/staging/west/test`, but not
|
||||
// `/staging` or `/prod/west`.
|
||||
Mode_MODE_DESCENDANTS Mode = 4
|
||||
// MODE_ANCESTORS matches the scope specified by Filter.Scope and all of its ancestors. For example,
|
||||
// a Filter.Scope of `/staging/west` would match `/staging/west` and `/staging`, but not
|
||||
// `/staging/west/test` or `/prod/west`.
|
||||
Mode_MODE_ANCESTORS Mode = 5
|
||||
// MODE_RELATIVES matches the scope specified by Filter.Scope, all of its ancestors, and all of its
|
||||
// descendants (i.e. any non-orthogonal scope). For example, a Filter.Scope of `/staging/west` would match
|
||||
// `/staging/west`, `/staging`, and `/staging/west/test, but not `/prod/west`.
|
||||
Mode_MODE_RELATIVES Mode = 6
|
||||
// MODE_UNSCOPED matches unscoped resources only. Filter.Scope must be empty when this mode is selected.
|
||||
// This is the safe default for watch/list requests initiated by unscoped callers that do not specify an
|
||||
// explicit filter.
|
||||
Mode_MODE_UNSCOPED Mode = 7
|
||||
// MODE_ALL matches all resources that the caller has permission to see (scoped and unscoped). Filter.Scope
|
||||
// must be empty when this mode is selected. This is the mode that most exhaustive user-facing views (e.g. `tctl get`)
|
||||
// should use.
|
||||
Mode_MODE_ALL Mode = 8
|
||||
)
|
||||
|
||||
// Enum value maps for Mode.
|
||||
var (
|
||||
Mode_name = map[int32]string{
|
||||
0: "MODE_UNSPECIFIED",
|
||||
1: "MODE_RESOURCES_SUBJECT_TO_SCOPE",
|
||||
2: "MODE_POLICIES_APPLICABLE_TO_SCOPE",
|
||||
3: "MODE_EXACT",
|
||||
4: "MODE_DESCENDANTS",
|
||||
5: "MODE_ANCESTORS",
|
||||
6: "MODE_RELATIVES",
|
||||
7: "MODE_UNSCOPED",
|
||||
8: "MODE_ALL",
|
||||
}
|
||||
Mode_value = map[string]int32{
|
||||
"MODE_UNSPECIFIED": 0,
|
||||
"MODE_RESOURCES_SUBJECT_TO_SCOPE": 1,
|
||||
"MODE_POLICIES_APPLICABLE_TO_SCOPE": 2,
|
||||
"MODE_EXACT": 3,
|
||||
"MODE_DESCENDANTS": 4,
|
||||
"MODE_ANCESTORS": 5,
|
||||
"MODE_RELATIVES": 6,
|
||||
"MODE_UNSCOPED": 7,
|
||||
"MODE_ALL": 8,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -589,11 +631,17 @@ func (b0 SystemRoles_builder) Build() *SystemRoles {
|
||||
return m0
|
||||
}
|
||||
|
||||
// Filter is a query parameter that matches other scopes based on a specified scope and mode. Used for
|
||||
// filtering resources that are subject to or policies that apply to a given scope.
|
||||
// Filter is a query parameter that matches scopes based on a specified scope and mode. See the
|
||||
// Mode enum for the semantics of each matching mode, and the 'lib/scopes' package (MatchScope/ValidateFilter)
|
||||
// for the authoritative matching and validation logic.
|
||||
//
|
||||
// Conventionally, list and watch requests for scope-aware types include a `scope_filter` field of this type
|
||||
// which matches the resource scope of the associated resource type. Some APIs may also support additional
|
||||
// filters (e.g. scoped roles can be filtered by assignable scope).
|
||||
type Filter struct {
|
||||
state protoimpl.MessageState `protogen:"hybrid.v1"`
|
||||
// Scope is the scope value to match against.
|
||||
// Scope is the scope value to match against. Must be empty for MODE_UNSCOPED, MODE_ALL, and
|
||||
// MODE_UNSPECIFIED, and non-empty for all relationship-based modes.
|
||||
Scope string `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"`
|
||||
// Mode determines the mode of matching.
|
||||
Mode Mode `protobuf:"varint,2,opt,name=mode,proto3,enum=teleport.scopes.v1.Mode" json:"mode,omitempty"`
|
||||
@@ -651,7 +699,8 @@ func (x *Filter) SetMode(v Mode) {
|
||||
type Filter_builder struct {
|
||||
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
|
||||
|
||||
// Scope is the scope value to match against.
|
||||
// Scope is the scope value to match against. Must be empty for MODE_UNSCOPED, MODE_ALL, and
|
||||
// MODE_UNSPECIFIED, and non-empty for all relationship-based modes.
|
||||
Scope string
|
||||
// Mode determines the mode of matching.
|
||||
Mode Mode
|
||||
@@ -701,11 +750,17 @@ const file_teleport_scopes_v1_scopes_proto_rawDesc = "" +
|
||||
"\aPinKind\x12\x18\n" +
|
||||
"\x14PIN_KIND_UNSPECIFIED\x10\x00\x12\x11\n" +
|
||||
"\rPIN_KIND_USER\x10\x01\x12\x12\n" +
|
||||
"\x0ePIN_KIND_AGENT\x10\x02*h\n" +
|
||||
"\x0ePIN_KIND_AGENT\x10\x02*\xdd\x01\n" +
|
||||
"\x04Mode\x12\x14\n" +
|
||||
"\x10MODE_UNSPECIFIED\x10\x00\x12#\n" +
|
||||
"\x1fMODE_RESOURCES_SUBJECT_TO_SCOPE\x10\x01\x12%\n" +
|
||||
"!MODE_POLICIES_APPLICABLE_TO_SCOPE\x10\x02BPZNgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1;scopesv1b\x06proto3"
|
||||
"\x10MODE_UNSPECIFIED\x10\x00\x12)\n" +
|
||||
"!MODE_POLICIES_APPLICABLE_TO_SCOPE\x10\x02\x1a\x02\b\x01\x12\x0e\n" +
|
||||
"\n" +
|
||||
"MODE_EXACT\x10\x03\x12\x14\n" +
|
||||
"\x10MODE_DESCENDANTS\x10\x04\x12\x12\n" +
|
||||
"\x0eMODE_ANCESTORS\x10\x05\x12\x12\n" +
|
||||
"\x0eMODE_RELATIVES\x10\x06\x12\x11\n" +
|
||||
"\rMODE_UNSCOPED\x10\a\x12\f\n" +
|
||||
"\bMODE_ALL\x10\b\"\x04\b\x01\x10\x01*\x1fMODE_RESOURCES_SUBJECT_TO_SCOPEBPZNgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1;scopesv1b\x06proto3"
|
||||
|
||||
var file_teleport_scopes_v1_scopes_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_teleport_scopes_v1_scopes_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
|
||||
@@ -86,37 +86,79 @@ func (x PinKind) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Mode determines the mode of scoping when a query specifies a scope. When a query specifies a scope,
|
||||
// one of two questions is typically trying to be answered. Either, what resources are "in" and/or "subject to"
|
||||
// a given scope, or what policies are "applicable to" a given scope.
|
||||
// Mode determines how a Filter matches resource scopes relative to the scope specified by the
|
||||
// filter. Most modes select resources by their relationship to Filter.Scope (exact, descendants,
|
||||
// ancestors, or all relatives). Two special modes (MODE_UNSCOPED and MODE_ALL) do not select by
|
||||
// relationship and instead match the unscoped subset or everything the caller may see, respectively.
|
||||
//
|
||||
// NOTE: Our internals treat an unspecified filter and a MODE_ALL filter is both being "match all",
|
||||
// *however* the API/authz layer treats them differently. A MODE_ALL filter is interpreted as being
|
||||
// intentional. However, an omitted filter is replaced by an identity-dependent default (MODE_EXACT
|
||||
// for scoped callers and MODE_UNSCOPED for unscoped callers). This conservative defaulting is a key
|
||||
// component of the backwards comatibility and safety story for scope-enabled APIs. Any callsite being
|
||||
// updated to use an inclusive filtering mode like MODE_ALL must be carefully validated to ensure that
|
||||
// it is safe in the context of issues like the "confused deputy" problem.
|
||||
type Mode int32
|
||||
|
||||
const (
|
||||
// MODE_UNSPECIFIED indicates that no scope-based filtering has been specified.
|
||||
// MODE_UNSPECIFIED indicates that no scope-based filtering has been specified. Cache/backend impls
|
||||
// treat this as equivalent to MODE_ALL. The API/authz layer replaces this with one of MODE_EXACT or
|
||||
// MODE_UNSCOPED depending on whether the caller is scoped or unscoped, respectively.
|
||||
Mode_MODE_UNSPECIFIED Mode = 0
|
||||
// MODE_RESOURCES_SUBJECT_TO_SCOPE matches scopes by the resource subjugation rules. In the
|
||||
// terminology of scope comparison, this means Equivalent and Descendant scopes. This is the mode that most
|
||||
// user-facing scoped queries should use, as it intuitively answers the question of "what is the contents of
|
||||
// scope X?". See the 'lib/scopes' package for more detailed discussion of scope comparison/heirarchy.
|
||||
Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE Mode = 1
|
||||
// MODE_POLICIES_APPLICABLE_TO_SCOPE matches scopes by the policy application rules. In the
|
||||
// terminology of scope comparison, this means Ancestor and Equivalent scopes. This is the mode that most caching
|
||||
// and access-control related queries should use as it answers the question "what policies might affect access to
|
||||
// this resource at scope X?". See the 'lib/scopes' package for more detailed discussion of scope comparison/heirarchy.
|
||||
//
|
||||
// Deprecated: Marked as deprecated in teleport/scopes/v1/scopes.proto.
|
||||
Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE Mode = 2
|
||||
// MODE_EXACT matches resources in the exact scope specified by Filter.Scope (Equivalent only). This is
|
||||
// the minimal/safe default for watch/list requests initiated by scoped callers that do not specify an
|
||||
// explicit filter.
|
||||
Mode_MODE_EXACT Mode = 3
|
||||
// MODE_DESCENDANTS matches the scope specified by Filter.Scope and all of its descendants. For example,
|
||||
// a Filter.Scope of `/staging/west` would match `/staging/west` and `/staging/west/test`, but not
|
||||
// `/staging` or `/prod/west`.
|
||||
Mode_MODE_DESCENDANTS Mode = 4
|
||||
// MODE_ANCESTORS matches the scope specified by Filter.Scope and all of its ancestors. For example,
|
||||
// a Filter.Scope of `/staging/west` would match `/staging/west` and `/staging`, but not
|
||||
// `/staging/west/test` or `/prod/west`.
|
||||
Mode_MODE_ANCESTORS Mode = 5
|
||||
// MODE_RELATIVES matches the scope specified by Filter.Scope, all of its ancestors, and all of its
|
||||
// descendants (i.e. any non-orthogonal scope). For example, a Filter.Scope of `/staging/west` would match
|
||||
// `/staging/west`, `/staging`, and `/staging/west/test, but not `/prod/west`.
|
||||
Mode_MODE_RELATIVES Mode = 6
|
||||
// MODE_UNSCOPED matches unscoped resources only. Filter.Scope must be empty when this mode is selected.
|
||||
// This is the safe default for watch/list requests initiated by unscoped callers that do not specify an
|
||||
// explicit filter.
|
||||
Mode_MODE_UNSCOPED Mode = 7
|
||||
// MODE_ALL matches all resources that the caller has permission to see (scoped and unscoped). Filter.Scope
|
||||
// must be empty when this mode is selected. This is the mode that most exhaustive user-facing views (e.g. `tctl get`)
|
||||
// should use.
|
||||
Mode_MODE_ALL Mode = 8
|
||||
)
|
||||
|
||||
// Enum value maps for Mode.
|
||||
var (
|
||||
Mode_name = map[int32]string{
|
||||
0: "MODE_UNSPECIFIED",
|
||||
1: "MODE_RESOURCES_SUBJECT_TO_SCOPE",
|
||||
2: "MODE_POLICIES_APPLICABLE_TO_SCOPE",
|
||||
3: "MODE_EXACT",
|
||||
4: "MODE_DESCENDANTS",
|
||||
5: "MODE_ANCESTORS",
|
||||
6: "MODE_RELATIVES",
|
||||
7: "MODE_UNSCOPED",
|
||||
8: "MODE_ALL",
|
||||
}
|
||||
Mode_value = map[string]int32{
|
||||
"MODE_UNSPECIFIED": 0,
|
||||
"MODE_RESOURCES_SUBJECT_TO_SCOPE": 1,
|
||||
"MODE_POLICIES_APPLICABLE_TO_SCOPE": 2,
|
||||
"MODE_EXACT": 3,
|
||||
"MODE_DESCENDANTS": 4,
|
||||
"MODE_ANCESTORS": 5,
|
||||
"MODE_RELATIVES": 6,
|
||||
"MODE_UNSCOPED": 7,
|
||||
"MODE_ALL": 8,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -571,8 +613,13 @@ func (b0 SystemRoles_builder) Build() *SystemRoles {
|
||||
return m0
|
||||
}
|
||||
|
||||
// Filter is a query parameter that matches other scopes based on a specified scope and mode. Used for
|
||||
// filtering resources that are subject to or policies that apply to a given scope.
|
||||
// Filter is a query parameter that matches scopes based on a specified scope and mode. See the
|
||||
// Mode enum for the semantics of each matching mode, and the 'lib/scopes' package (MatchScope/ValidateFilter)
|
||||
// for the authoritative matching and validation logic.
|
||||
//
|
||||
// Conventionally, list and watch requests for scope-aware types include a `scope_filter` field of this type
|
||||
// which matches the resource scope of the associated resource type. Some APIs may also support additional
|
||||
// filters (e.g. scoped roles can be filtered by assignable scope).
|
||||
type Filter struct {
|
||||
state protoimpl.MessageState `protogen:"opaque.v1"`
|
||||
xxx_hidden_Scope string `protobuf:"bytes,1,opt,name=scope,proto3"`
|
||||
@@ -631,7 +678,8 @@ func (x *Filter) SetMode(v Mode) {
|
||||
type Filter_builder struct {
|
||||
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
|
||||
|
||||
// Scope is the scope value to match against.
|
||||
// Scope is the scope value to match against. Must be empty for MODE_UNSCOPED, MODE_ALL, and
|
||||
// MODE_UNSPECIFIED, and non-empty for all relationship-based modes.
|
||||
Scope string
|
||||
// Mode determines the mode of matching.
|
||||
Mode Mode
|
||||
@@ -681,11 +729,17 @@ const file_teleport_scopes_v1_scopes_proto_rawDesc = "" +
|
||||
"\aPinKind\x12\x18\n" +
|
||||
"\x14PIN_KIND_UNSPECIFIED\x10\x00\x12\x11\n" +
|
||||
"\rPIN_KIND_USER\x10\x01\x12\x12\n" +
|
||||
"\x0ePIN_KIND_AGENT\x10\x02*h\n" +
|
||||
"\x0ePIN_KIND_AGENT\x10\x02*\xdd\x01\n" +
|
||||
"\x04Mode\x12\x14\n" +
|
||||
"\x10MODE_UNSPECIFIED\x10\x00\x12#\n" +
|
||||
"\x1fMODE_RESOURCES_SUBJECT_TO_SCOPE\x10\x01\x12%\n" +
|
||||
"!MODE_POLICIES_APPLICABLE_TO_SCOPE\x10\x02BPZNgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1;scopesv1b\x06proto3"
|
||||
"\x10MODE_UNSPECIFIED\x10\x00\x12)\n" +
|
||||
"!MODE_POLICIES_APPLICABLE_TO_SCOPE\x10\x02\x1a\x02\b\x01\x12\x0e\n" +
|
||||
"\n" +
|
||||
"MODE_EXACT\x10\x03\x12\x14\n" +
|
||||
"\x10MODE_DESCENDANTS\x10\x04\x12\x12\n" +
|
||||
"\x0eMODE_ANCESTORS\x10\x05\x12\x12\n" +
|
||||
"\x0eMODE_RELATIVES\x10\x06\x12\x11\n" +
|
||||
"\rMODE_UNSCOPED\x10\a\x12\f\n" +
|
||||
"\bMODE_ALL\x10\b\"\x04\b\x01\x10\x01*\x1fMODE_RESOURCES_SUBJECT_TO_SCOPEBPZNgithub.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1;scopesv1b\x06proto3"
|
||||
|
||||
var file_teleport_scopes_v1_scopes_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_teleport_scopes_v1_scopes_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
|
||||
@@ -76,6 +76,9 @@ message GetScopedRoleResponse {
|
||||
|
||||
// ListScopedRolesRequest is the request to list scoped roles.
|
||||
message ListScopedRolesRequest {
|
||||
reserved 4;
|
||||
reserved "assignable_scope";
|
||||
|
||||
// PageSize is the maximum number of results to return.
|
||||
int32 page_size = 1;
|
||||
|
||||
@@ -83,14 +86,17 @@ message ListScopedRolesRequest {
|
||||
string page_token = 2;
|
||||
|
||||
// ResourceScope filters roles by their resource scope if specified.
|
||||
teleport.scopes.v1.Filter resource_scope = 3;
|
||||
|
||||
// AssignableScope filters roles by their assignable scope if specified.
|
||||
teleport.scopes.v1.Filter assignable_scope = 4;
|
||||
teleport.scopes.v1.Filter resource_scope = 3 [deprecated = true];
|
||||
|
||||
// NameFilter filters roles by their name using a case-insensitive substring
|
||||
// match if specified.
|
||||
string name_filter = 5;
|
||||
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
teleport.scopes.v1.Filter scope_filter = 6;
|
||||
}
|
||||
|
||||
// ListScopedRolesResponse is the response to list scoped roles.
|
||||
@@ -166,19 +172,15 @@ message GetScopedRoleAssignmentResponse {
|
||||
|
||||
// ListScopedRoleAssignmentsRequest is the request to list scoped role assignments.
|
||||
message ListScopedRoleAssignmentsRequest {
|
||||
reserved 3, 4;
|
||||
reserved "resource_scope", "assigned_scope";
|
||||
|
||||
// PageSize is the maximum number of results to return.
|
||||
int32 page_size = 1;
|
||||
|
||||
// PageToken is the pagination cursor used to start from where a previous request left off.
|
||||
string page_token = 2;
|
||||
|
||||
// ResourceScope filters assignments by their resource scope if specified.
|
||||
teleport.scopes.v1.Filter resource_scope = 3;
|
||||
|
||||
// AssignedScope filters assignments by the scopes they assign to if specified (note: matches assignment
|
||||
// resources with 1 or more maching scopes, not all scopes within the assignment will necessarily match).
|
||||
teleport.scopes.v1.Filter assigned_scope = 4;
|
||||
|
||||
// User optionally limits the list to assignments for a specific user.
|
||||
string user = 5;
|
||||
|
||||
@@ -190,6 +192,17 @@ message ListScopedRoleAssignmentsRequest {
|
||||
// is specifically used to support users discovering where they have been granted privileges across scopes,
|
||||
// and is not intended for general use.
|
||||
bool all_caller_assignments = 7;
|
||||
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
teleport.scopes.v1.Filter scope_filter = 8;
|
||||
|
||||
// AssignedScopeFilter filters assignments by the scopes they assign to if specified (note: matches
|
||||
// assignment resources with 1 or more matching scopes, not all scopes within the assignment will
|
||||
// necessarily match).
|
||||
teleport.scopes.v1.Filter assigned_scope_filter = 9;
|
||||
}
|
||||
|
||||
// ListScopedRoleAssignmentsResponse is the response to list scoped role assignments.
|
||||
|
||||
@@ -62,11 +62,8 @@ message GetScopedTokenResponse {
|
||||
|
||||
// ListScopedTokensRequest is the request to list scoped tokens.
|
||||
message ListScopedTokensRequest {
|
||||
// Filter tokens by their resource scope.
|
||||
teleport.scopes.v1.Filter resource_scope = 1;
|
||||
|
||||
// Filter tokens by their assigned scope.
|
||||
teleport.scopes.v1.Filter assigned_scope = 2;
|
||||
reserved 1, 2;
|
||||
reserved "resource_scope", "assigned_scope";
|
||||
|
||||
// The pagination cursor.
|
||||
string cursor = 3;
|
||||
@@ -82,6 +79,15 @@ message ListScopedTokensRequest {
|
||||
|
||||
// If true, include the token secrets in the response.
|
||||
bool with_secrets = 7;
|
||||
|
||||
// ScopeFilter is the primary scope filter. Filtering is performed against the scope of the
|
||||
// resource itself. Defaults to one of EXACT or UNSCOPED depending on wether the caller is
|
||||
// a scoped or unscoped identity. Exhaustive user-facing views (e.g. `tctl get`) should
|
||||
// specify mode ALL.
|
||||
teleport.scopes.v1.Filter scope_filter = 8;
|
||||
|
||||
// AssignedScopeFilter filters tokens by their assigned scope if specified.
|
||||
teleport.scopes.v1.Filter assigned_scope_filter = 9;
|
||||
}
|
||||
|
||||
// ListScopedTokensResponse is the response to list scoped tokens.
|
||||
|
||||
@@ -92,30 +92,74 @@ message SystemRoles {
|
||||
repeated string additional = 2;
|
||||
}
|
||||
|
||||
// Mode determines the mode of scoping when a query specifies a scope. When a query specifies a scope,
|
||||
// one of two questions is typically trying to be answered. Either, what resources are "in" and/or "subject to"
|
||||
// a given scope, or what policies are "applicable to" a given scope.
|
||||
// Mode determines how a Filter matches resource scopes relative to the scope specified by the
|
||||
// filter. Most modes select resources by their relationship to Filter.Scope (exact, descendants,
|
||||
// ancestors, or all relatives). Two special modes (MODE_UNSCOPED and MODE_ALL) do not select by
|
||||
// relationship and instead match the unscoped subset or everything the caller may see, respectively.
|
||||
//
|
||||
// NOTE: Our internals treat an unspecified filter and a MODE_ALL filter is both being "match all",
|
||||
// *however* the API/authz layer treats them differently. A MODE_ALL filter is interpreted as being
|
||||
// intentional. However, an omitted filter is replaced by an identity-dependent default (MODE_EXACT
|
||||
// for scoped callers and MODE_UNSCOPED for unscoped callers). This conservative defaulting is a key
|
||||
// component of the backwards comatibility and safety story for scope-enabled APIs. Any callsite being
|
||||
// updated to use an inclusive filtering mode like MODE_ALL must be carefully validated to ensure that
|
||||
// it is safe in the context of issues like the "confused deputy" problem.
|
||||
enum Mode {
|
||||
// MODE_UNSPECIFIED indicates that no scope-based filtering has been specified.
|
||||
MODE_UNSPECIFIED = 0;
|
||||
reserved 1;
|
||||
reserved "MODE_RESOURCES_SUBJECT_TO_SCOPE";
|
||||
|
||||
// MODE_RESOURCES_SUBJECT_TO_SCOPE matches scopes by the resource subjugation rules. In the
|
||||
// terminology of scope comparison, this means Equivalent and Descendant scopes. This is the mode that most
|
||||
// user-facing scoped queries should use, as it intuitively answers the question of "what is the contents of
|
||||
// scope X?". See the 'lib/scopes' package for more detailed discussion of scope comparison/heirarchy.
|
||||
MODE_RESOURCES_SUBJECT_TO_SCOPE = 1;
|
||||
// MODE_UNSPECIFIED indicates that no scope-based filtering has been specified. Cache/backend impls
|
||||
// treat this as equivalent to MODE_ALL. The API/authz layer replaces this with one of MODE_EXACT or
|
||||
// MODE_UNSCOPED depending on whether the caller is scoped or unscoped, respectively.
|
||||
MODE_UNSPECIFIED = 0;
|
||||
|
||||
// MODE_POLICIES_APPLICABLE_TO_SCOPE matches scopes by the policy application rules. In the
|
||||
// terminology of scope comparison, this means Ancestor and Equivalent scopes. This is the mode that most caching
|
||||
// and access-control related queries should use as it answers the question "what policies might affect access to
|
||||
// this resource at scope X?". See the 'lib/scopes' package for more detailed discussion of scope comparison/heirarchy.
|
||||
MODE_POLICIES_APPLICABLE_TO_SCOPE = 2;
|
||||
MODE_POLICIES_APPLICABLE_TO_SCOPE = 2 [deprecated = true];
|
||||
|
||||
// MODE_EXACT matches resources in the exact scope specified by Filter.Scope (Equivalent only). This is
|
||||
// the minimal/safe default for watch/list requests initiated by scoped callers that do not specify an
|
||||
// explicit filter.
|
||||
MODE_EXACT = 3;
|
||||
|
||||
// MODE_DESCENDANTS matches the scope specified by Filter.Scope and all of its descendants. For example,
|
||||
// a Filter.Scope of `/staging/west` would match `/staging/west` and `/staging/west/test`, but not
|
||||
// `/staging` or `/prod/west`.
|
||||
MODE_DESCENDANTS = 4;
|
||||
|
||||
// MODE_ANCESTORS matches the scope specified by Filter.Scope and all of its ancestors. For example,
|
||||
// a Filter.Scope of `/staging/west` would match `/staging/west` and `/staging`, but not
|
||||
// `/staging/west/test` or `/prod/west`.
|
||||
MODE_ANCESTORS = 5;
|
||||
|
||||
// MODE_RELATIVES matches the scope specified by Filter.Scope, all of its ancestors, and all of its
|
||||
// descendants (i.e. any non-orthogonal scope). For example, a Filter.Scope of `/staging/west` would match
|
||||
// `/staging/west`, `/staging`, and `/staging/west/test, but not `/prod/west`.
|
||||
MODE_RELATIVES = 6;
|
||||
|
||||
// MODE_UNSCOPED matches unscoped resources only. Filter.Scope must be empty when this mode is selected.
|
||||
// This is the safe default for watch/list requests initiated by unscoped callers that do not specify an
|
||||
// explicit filter.
|
||||
MODE_UNSCOPED = 7;
|
||||
|
||||
// MODE_ALL matches all resources that the caller has permission to see (scoped and unscoped). Filter.Scope
|
||||
// must be empty when this mode is selected. This is the mode that most exhaustive user-facing views (e.g. `tctl get`)
|
||||
// should use.
|
||||
MODE_ALL = 8;
|
||||
}
|
||||
|
||||
// Filter is a query parameter that matches other scopes based on a specified scope and mode. Used for
|
||||
// filtering resources that are subject to or policies that apply to a given scope.
|
||||
// Filter is a query parameter that matches scopes based on a specified scope and mode. See the
|
||||
// Mode enum for the semantics of each matching mode, and the 'lib/scopes' package (MatchScope/ValidateFilter)
|
||||
// for the authoritative matching and validation logic.
|
||||
//
|
||||
// Conventionally, list and watch requests for scope-aware types include a `scope_filter` field of this type
|
||||
// which matches the resource scope of the associated resource type. Some APIs may also support additional
|
||||
// filters (e.g. scoped roles can be filtered by assignable scope).
|
||||
message Filter {
|
||||
// Scope is the scope value to match against.
|
||||
// Scope is the scope value to match against. Must be empty for MODE_UNSCOPED, MODE_ALL, and
|
||||
// MODE_UNSPECIFIED, and non-empty for all relationship-based modes.
|
||||
string scope = 1;
|
||||
|
||||
// Mode determines the mode of matching.
|
||||
|
||||
@@ -1072,7 +1072,9 @@ func TestBasicSSHScopedLogin(t *testing.T) {
|
||||
timeout := time.After(30 * time.Second)
|
||||
for {
|
||||
unseen := slices.Clone(assignmentIDs)
|
||||
for assignment, err := range scopedutils.RangeScopedRoleAssignments(ctx, adminClient.ScopedAccessServiceClient(), &scopedaccessv1.ListScopedRoleAssignmentsRequest{}) {
|
||||
for assignment, err := range scopedutils.RangeScopedRoleAssignments(ctx, adminClient.ScopedAccessServiceClient(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()) {
|
||||
require.NoError(t, err)
|
||||
id := assignment.GetMetadata().GetName()
|
||||
unseen = slices.DeleteFunc(unseen, func(unseenID string) bool { return id == unseenID })
|
||||
|
||||
@@ -397,12 +397,16 @@ func (s *Server) ListScopedRoleAssignments(ctx context.Context, req *scopedacces
|
||||
}
|
||||
}
|
||||
|
||||
// list method scope filters must use identity-based defaults per RFD 0229i
|
||||
req.SetScopeFilter(authzContext.CheckerContext.ResolveScopeFilter(req.GetScopeFilter()))
|
||||
|
||||
// list scoped role assignments with a filter that only passes assignments the user has access to.
|
||||
rsp, err := s.cfg.Reader.ListScopedRoleAssignmentsWithFilter(ctx, req, func(assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
if req.GetAllCallerAssignments() {
|
||||
// note that this short-circuit doesn't just bypass verb checks, it also bypasses scope pinning. this is
|
||||
// intended behavior and an important part of what makes the all_caller_assignments mode useful, as it allows
|
||||
// users to get an overview of their available privileges across all scopes.
|
||||
// users to get an overview of their available privileges across all scopes (assuming the scope filter mode
|
||||
// has been set to ALL).
|
||||
return authzContext.User.GetName() == assignment.GetSpec().GetUser()
|
||||
}
|
||||
err := authzContext.CheckerContext.Decision(ctx, assignment.GetScope(), func(checker *services.ScopedAccessChecker) error {
|
||||
@@ -431,6 +435,15 @@ func (s *Server) ListScopedRoles(ctx context.Context, req *scopedaccessv1.ListSc
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// the resource_scope field was renamed to scope_filter and is now deprecated. honor it as equivalent to
|
||||
// scope_filter for back-compat with clients that have not yet been updated to set scope_filter.
|
||||
if req.HasResourceScope() && !req.HasScopeFilter() { //nolint:staticcheck // SA1019. Reading deprecated field for backwards compatibility.
|
||||
req.SetScopeFilter(req.GetResourceScope()) //nolint:staticcheck // SA1019. Reading deprecated field for backwards compatibility.
|
||||
}
|
||||
|
||||
// list method scope filters must use identity-based defaults per RFD 0229i
|
||||
req.SetScopeFilter(authzContext.CheckerContext.ResolveScopeFilter(req.GetScopeFilter()))
|
||||
|
||||
// list scoped roles with a filter that only passes roles the user has access to.
|
||||
rsp, err := s.cfg.Reader.ListScopedRolesWithFilter(ctx, req, func(role *scopedaccessv1.ScopedRole) bool {
|
||||
err := authzContext.CheckerContext.Decision(ctx, role.GetScope(), func(checker *services.ScopedAccessChecker) error {
|
||||
|
||||
@@ -981,12 +981,20 @@ func TestUnscopedBasics(t *testing.T) {
|
||||
require.Equal(t, "some-role", rrsp.GetRole().GetMetadata().GetName())
|
||||
|
||||
// verify that admin can list the role
|
||||
lrsp, err := srvAlice.ListScopedRoles(ctx, &scopedaccessv1.ListScopedRolesRequest{})
|
||||
lrsp, err := srvAlice.ListScopedRoles(ctx, scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, lrsp.GetNextPageToken())
|
||||
require.Len(t, lrsp.GetRoles(), 1)
|
||||
require.Equal(t, "some-role", lrsp.GetRoles()[0].GetMetadata().GetName())
|
||||
|
||||
// verify that an omitted filter defaults to MODE_UNSCOPED for unscoped caller, which matches no
|
||||
// scoped resources.
|
||||
lrsp, err = srvAlice.ListScopedRoles(ctx, &scopedaccessv1.ListScopedRolesRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, lrsp.GetRoles())
|
||||
|
||||
// verify that auditor cannot create a role
|
||||
_, err = srvBob.CreateScopedRole(ctx, scopedaccessv1.CreateScopedRoleRequest_builder{
|
||||
Role: scopedaccessv1.ScopedRole_builder{
|
||||
@@ -1012,7 +1020,9 @@ func TestUnscopedBasics(t *testing.T) {
|
||||
require.Equal(t, crsp.GetRole().GetMetadata().GetName(), rrsp.GetRole().GetMetadata().GetName())
|
||||
|
||||
// verify that auditor can list the admin-created role
|
||||
lrsp, err = srvBob.ListScopedRoles(ctx, &scopedaccessv1.ListScopedRolesRequest{})
|
||||
lrsp, err = srvBob.ListScopedRoles(ctx, scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, lrsp.GetNextPageToken())
|
||||
require.Len(t, lrsp.GetRoles(), 1)
|
||||
@@ -1056,7 +1066,9 @@ func TestUnscopedBasics(t *testing.T) {
|
||||
require.Equal(t, acrsp.GetAssignment().GetMetadata().GetName(), rasp.GetAssignment().GetMetadata().GetName())
|
||||
|
||||
// verify that admin can list the assignment
|
||||
lasp, err := srvAlice.ListScopedRoleAssignments(ctx, &scopedaccessv1.ListScopedRoleAssignmentsRequest{})
|
||||
lasp, err := srvAlice.ListScopedRoleAssignments(ctx, scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, lasp.GetNextPageToken())
|
||||
require.Len(t, lasp.GetAssignments(), 1)
|
||||
@@ -1095,7 +1107,9 @@ func TestUnscopedBasics(t *testing.T) {
|
||||
require.Equal(t, acrsp.GetAssignment().GetMetadata().GetName(), rasp.GetAssignment().GetMetadata().GetName())
|
||||
|
||||
// verify that auditor can list the admin-created assignment
|
||||
lasp, err = srvBob.ListScopedRoleAssignments(ctx, &scopedaccessv1.ListScopedRoleAssignmentsRequest{})
|
||||
lasp, err = srvBob.ListScopedRoleAssignments(ctx, scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, lasp.GetNextPageToken())
|
||||
require.Len(t, lasp.GetAssignments(), 1)
|
||||
@@ -1359,3 +1373,81 @@ func waitForAssignmentCondition(t *testing.T, reader services.ScopedRoleAssignme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestListScopedRolesFilterDefaulting verifies the identity-based defaulting for the primary scope filter
|
||||
// is in effect for ListScopedRoles.
|
||||
func TestListScopedRolesFilterDefaulting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := t.Context()
|
||||
bk := newBackendPack(t)
|
||||
defer bk.Close()
|
||||
|
||||
// staging-admin lives at /staging and grants read/list on scoped roles. west-role lives at the
|
||||
// descendant scope /staging/west and exists only as data to be listed.
|
||||
roles := []*scopedaccessv1.ScopedRole{
|
||||
scopedaccessv1.ScopedRole_builder{
|
||||
Kind: scopedaccess.KindScopedRole,
|
||||
Metadata: headerv1.Metadata_builder{Name: "staging-admin"}.Build(),
|
||||
Scope: "/staging",
|
||||
Spec: scopedaccessv1.ScopedRoleSpec_builder{
|
||||
AssignableScopes: []string{"/staging"},
|
||||
Rules: []*scopedaccessv1.ScopedRule{
|
||||
scopedaccessv1.ScopedRule_builder{
|
||||
Resources: []string{scopedaccess.KindScopedRole},
|
||||
Verbs: []string{types.VerbReadNoSecrets, types.VerbList},
|
||||
}.Build(),
|
||||
},
|
||||
}.Build(),
|
||||
Version: types.V1,
|
||||
}.Build(),
|
||||
scopedaccessv1.ScopedRole_builder{
|
||||
Kind: scopedaccess.KindScopedRole,
|
||||
Metadata: headerv1.Metadata_builder{Name: "west-role"}.Build(),
|
||||
Scope: "/staging/west",
|
||||
Spec: scopedaccessv1.ScopedRoleSpec_builder{
|
||||
AssignableScopes: []string{"/staging/west"},
|
||||
}.Build(),
|
||||
Version: types.V1,
|
||||
}.Build(),
|
||||
}
|
||||
for _, role := range roles {
|
||||
_, err := bk.service.CreateScopedRole(ctx, scopedaccessv1.CreateScopedRoleRequest_builder{Role: role}.Build())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
waitForRoleCondition(t, bk.cache, func(roles []*scopedaccessv1.ScopedRole) bool {
|
||||
return len(roles) == 2
|
||||
})
|
||||
|
||||
// caller is pinned to /staging and holds staging-admin, so it can read roles at /staging and its
|
||||
// descendants (including west-role at /staging/west).
|
||||
srv := newServerForIdentity(t, bk, &services.AccessInfo{
|
||||
ScopePin: scopesv1.Pin_builder{
|
||||
Kind: scopesv1.PinKind_PIN_KIND_USER,
|
||||
Scope: "/staging",
|
||||
AssignmentTree: pinning.AssignmentTreeFromMap(map[string]map[string][]string{
|
||||
"/staging": {"/staging": {"staging-admin"}},
|
||||
}),
|
||||
}.Build(),
|
||||
Username: "alice",
|
||||
})
|
||||
|
||||
collectNames := func(req *scopedaccessv1.ListScopedRolesRequest) []string {
|
||||
rsp, err := srv.ListScopedRoles(ctx, req)
|
||||
require.NoError(t, err)
|
||||
var names []string
|
||||
for _, role := range rsp.GetRoles() {
|
||||
names = append(names, role.GetMetadata().GetName())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// omitted filter defaults to MODE_EXACT at the pinned scope (/staging), so only the /staging role is
|
||||
// returned - the readable descendant role at /staging/west is excluded.
|
||||
require.Equal(t, []string{"staging-admin"}, collectNames(&scopedaccessv1.ListScopedRolesRequest{}))
|
||||
|
||||
// MODE_ALL returns everything the caller may read across scopes, including the descendant role.
|
||||
require.ElementsMatch(t, []string{"staging-admin", "west-role"}, collectNames(scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()))
|
||||
}
|
||||
|
||||
@@ -268,6 +268,9 @@ func (s *Server) ListScopedTokens(ctx context.Context, req *scopedjoiningv1.List
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// list method scope filters must use identity-based defaults per RFD 0229i
|
||||
req.SetScopeFilter(authzContext.CheckerContext.ResolveScopeFilter(req.GetScopeFilter()))
|
||||
|
||||
limit := int(req.GetLimit())
|
||||
if limit == 0 {
|
||||
limit = defaultTokenPageSize
|
||||
|
||||
@@ -159,8 +159,8 @@ func TestScopedJoiningService(t *testing.T) {
|
||||
// list tokens while filtering their resource scope
|
||||
res, err := service.ListScopedTokens(ctx, joiningv1.ListScopedTokensRequest_builder{
|
||||
WithSecrets: true,
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/staging/cc",
|
||||
}.Build(),
|
||||
}.Build())
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
)
|
||||
|
||||
// MatcHSecondaryAssignmentFilters provides a single source of truth for a subset of the matching logic needed to implement
|
||||
// the ListScopedRoleAssignments RPC. How the primary scope filter functions differs between cache/backend impls, but the
|
||||
// secondary filters (e.g. user name) function identically, so we centralize them here.
|
||||
func MatchSecondaryAssignmentFilters(req *scopedaccessv1.ListScopedRoleAssignmentsRequest, assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
if req.GetUser() != "" && assignment.GetSpec().GetUser() != req.GetUser() {
|
||||
return false
|
||||
}
|
||||
|
||||
if req.GetRole() != "" {
|
||||
if !slices.ContainsFunc(assignment.GetSpec().GetAssignments(), func(a *scopedaccessv1.Assignment) bool {
|
||||
return a.GetRole() == req.GetRole()
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if asf := req.GetAssignedScopeFilter(); !scopes.IsMatchAll(asf) {
|
||||
// we only evaluate non-wildcard filters here because we don't want to suppress assignments with
|
||||
// empty sub-assignment sets unless the filter is restrictive.
|
||||
if !slices.ContainsFunc(assignment.GetSpec().GetAssignments(), func(a *scopedaccessv1.Assignment) bool {
|
||||
return scopes.MatchScope(asf, a.GetScope())
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
)
|
||||
|
||||
func TestMatchSecondaryAssignmentFilters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assignment := scopedaccessv1.ScopedRoleAssignment_builder{
|
||||
Spec: scopedaccessv1.ScopedRoleAssignmentSpec_builder{
|
||||
User: "alice",
|
||||
Assignments: []*scopedaccessv1.Assignment{
|
||||
scopedaccessv1.Assignment_builder{Role: "role-a", Scope: "/aa"}.Build(),
|
||||
scopedaccessv1.Assignment_builder{Role: "role-b", Scope: "/aa/bb"}.Build(),
|
||||
},
|
||||
}.Build(),
|
||||
}.Build()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req *scopedaccessv1.ListScopedRoleAssignmentsRequest
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no filters matches",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{}.Build(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "matching user",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{User: "alice"}.Build(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-matching user",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{User: "bob"}.Build(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "matching role",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{Role: "role-b"}.Build(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-matching role",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{Role: "role-x"}.Build(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "matching assigned scope",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT, Scope: "/aa/bb"}.Build(),
|
||||
}.Build(),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-matching assigned scope",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT, Scope: "/cc"}.Build(),
|
||||
}.Build(),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "all filters together match",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
User: "alice",
|
||||
Role: "role-a",
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_DESCENDANTS, Scope: "/aa"}.Build(),
|
||||
}.Build(),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, MatchSecondaryAssignmentFilters(tt.req, assignment))
|
||||
})
|
||||
}
|
||||
}
|
||||
+14
-41
@@ -21,14 +21,12 @@ package assignments
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
"github.com/gravitational/teleport/lib/scopes/cache"
|
||||
@@ -119,23 +117,21 @@ func (c *AssignmentCache) ListScopedRoleAssignmentsWithFilter(ctx context.Contex
|
||||
pageSize = maxPageSize
|
||||
}
|
||||
|
||||
if req.GetResourceScope() != nil && req.GetAssignedScope() != nil {
|
||||
return nil, trace.BadParameter("cannot filter by both resource scope and assigned scope simultaneously")
|
||||
// validate the secondary assigned-scope filter. the primary scope filter is validated by the
|
||||
// Cache.ResourcesMatchingScopeFilter method invoked below.
|
||||
if err := scopes.ValidateFilter(req.GetAssignedScopeFilter()); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
filter := func(assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
if req.GetUser() != "" && assignment.GetSpec().GetUser() != req.GetUser() {
|
||||
// primary scope filter matching is handled by the Cache.ResourcesMatchingScopeFilter method
|
||||
// invoked below. secondary filters (e.g. username) are applied here *after* the primary
|
||||
// has already been applied.
|
||||
if !scopedaccess.MatchSecondaryAssignmentFilters(req, assignment) {
|
||||
return false
|
||||
}
|
||||
|
||||
if req.GetRole() != "" {
|
||||
if !slices.ContainsFunc(assignment.GetSpec().GetAssignments(), func(a *scopedaccessv1.Assignment) bool { return a.GetRole() == req.GetRole() }) {
|
||||
// if the assignment does not have the requested role, skip it
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// apply the external filter after the basic request filter as the externally provided filter
|
||||
// apply the external filter after the secondary filters as the externally provided filter
|
||||
// is often more expensive to evaluate.
|
||||
if !externalFilter(assignment) {
|
||||
return false
|
||||
@@ -149,39 +145,16 @@ func (c *AssignmentCache) ListScopedRoleAssignmentsWithFilter(ctx context.Contex
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// resources subject to policy scope root is basically the scoped resource equivalent
|
||||
// of a "get all". this is a reasonable default for most queries.
|
||||
getter := c.cache.ResourcesSubjectToPolicyScope
|
||||
scope := scopes.Root
|
||||
|
||||
// if a resource scope filter has been provided, the user has specified a custom scope/mode to
|
||||
// query by.
|
||||
if req.GetResourceScope() != nil {
|
||||
// a resource-scope based filter has been provided
|
||||
switch req.GetResourceScope().GetMode() {
|
||||
case scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE:
|
||||
getter = c.cache.ResourcesSubjectToPolicyScope
|
||||
case scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE:
|
||||
getter = c.cache.PoliciesApplicableToResourceScope
|
||||
default:
|
||||
return nil, trace.BadParameter("unsupported or unspecified scoping mode %q in scoped role assignment resource scope filter", req.GetResourceScope().GetMode())
|
||||
}
|
||||
scope = req.GetResourceScope().GetScope()
|
||||
}
|
||||
|
||||
if req.GetAssignedScope() != nil {
|
||||
// NOTE: we eventually want to be able to query assignments by the scopes at which they assign roles,
|
||||
// instead of just resource scope. this is important for introspection/auditing but not necessary for
|
||||
// the core funcationality of teleport until we've fully migrated to PDP. For now, the implementation has
|
||||
// been deferred. (will require implementation of a more complex caching structure where each resource
|
||||
// will be associable with multiple scopes)
|
||||
return nil, trace.NotImplemented("assigned scope filtering for scoped role assignments is not yet supported")
|
||||
// the primary scope filter selects the cache traversal strategy.
|
||||
resources, err := c.cache.ResourcesMatchingScopeFilter(req.GetScopeFilter(), c.cache.WithFilter(filter), c.cache.WithCursor(cursor))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
var out []*scopedaccessv1.ScopedRoleAssignment
|
||||
var nextCursor cache.Cursor[string]
|
||||
Outer:
|
||||
for scope := range getter(scope, c.cache.WithFilter(filter), c.cache.WithCursor(cursor)) {
|
||||
for scope := range resources {
|
||||
for assignment := range scope.Items() {
|
||||
if len(out) == pageSize {
|
||||
nextCursor = cache.Cursor[string]{
|
||||
|
||||
+22
-22
@@ -214,8 +214,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
|
||||
// verify expected behavior for standard cursors in resources subject to scope mode
|
||||
rsp, err := cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:bob-02/dynamic@/aa",
|
||||
@@ -226,8 +226,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
|
||||
// try to inject a malicious root out-of-band cursor in resources subject to scope mode (no effect)
|
||||
rsp, err = cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:bob-01/dynamic@/",
|
||||
@@ -238,8 +238,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
|
||||
// try to inject a malicious orthogonal out-of-band cursor in resources subject to scope mode (no effect)
|
||||
rsp, err = cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:carol-01/dynamic@/bb",
|
||||
@@ -250,8 +250,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
|
||||
// verify expected behavior for standard cursors in policies applicable to scope mode
|
||||
rsp, err = cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:bob-01/dynamic@/",
|
||||
@@ -263,8 +263,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
// try to inject a malicious child out-of-band cursor in policies applicable to scope mode (effect is
|
||||
// to ignore all items in valid query path).
|
||||
rsp, err = cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:bob-03/dynamic@/aa/bb",
|
||||
@@ -276,8 +276,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
// try to inject a malicious orthogonal out-of-band cursor in policies applicable to scope mode (effect is to
|
||||
// ignore root, but process leaf normally).
|
||||
rsp, err = cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:carol-01/dynamic@/bb",
|
||||
@@ -288,8 +288,8 @@ func TestListScopedRoleAssignmentsScenarios(t *testing.T) {
|
||||
|
||||
// verify rejection of unknown cursor version
|
||||
_, err = cache.ListScopedRoleAssignments(t.Context(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v2:bob-02/dynamic@/aa",
|
||||
@@ -545,8 +545,8 @@ func TestListScopedRoleAssignmentsBasics(t *testing.T) {
|
||||
{
|
||||
name: "resource scope root",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -563,8 +563,8 @@ func TestListScopedRoleAssignmentsBasics(t *testing.T) {
|
||||
{
|
||||
name: "resource scope non-root",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -578,8 +578,8 @@ func TestListScopedRoleAssignmentsBasics(t *testing.T) {
|
||||
{
|
||||
name: "policy scope root",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -593,8 +593,8 @@ func TestListScopedRoleAssignmentsBasics(t *testing.T) {
|
||||
{
|
||||
name: "policy scope non-root",
|
||||
req: scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ func (c *AssignmentCache) PopulatePinnedAssignmentsForUser(ctx context.Context,
|
||||
var lastErr error
|
||||
|
||||
// all non-orthogonal assignments for this user *may* assign roles relevant to this pin
|
||||
assignments := c.cache.AllNonOrthogonalResources(pin.GetScope(), c.cache.WithFilter(func(assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
assignments := c.cache.ScopeAndRelatives(pin.GetScope(), c.cache.WithFilter(func(assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
return assignment.GetSpec().GetUser() == user
|
||||
}))
|
||||
|
||||
@@ -200,7 +200,7 @@ func (c *AssignmentCache) PopulatePinnedAssignmentsForBot(
|
||||
|
||||
// all non-orthogonal assignments for this bot *may* assign roles relevant to this pin
|
||||
wantBot := scopes.QualifiedName{Scope: botScope, Name: botName}.String()
|
||||
assignments := c.cache.AllNonOrthogonalResources(pin.GetScope(), c.cache.WithFilter(func(assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
assignments := c.cache.ScopeAndRelatives(pin.GetScope(), c.cache.WithFilter(func(assignment *scopedaccessv1.ScopedRoleAssignment) bool {
|
||||
// match on the full scope-qualified name. comparing the scope component
|
||||
// as well as the name mitigates name reuse attacks across scopes.
|
||||
return assignment.GetSpec().GetBot() == wantBot
|
||||
|
||||
Vendored
+99
-10
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
"github.com/gravitational/teleport/lib/utils/sortmap"
|
||||
)
|
||||
@@ -161,9 +162,96 @@ func (c *Cache[T, K]) Get(key K) (T, bool) {
|
||||
return c.cfg.Clone(value), true
|
||||
}
|
||||
|
||||
// PoliciesApplicableToResourceScope iterates over the cached items using policy-application rules (i.e.
|
||||
// a descending iteration from root, through the leaf of the specified scope).
|
||||
func (c *Cache[T, K]) PoliciesApplicableToResourceScope(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
// ResourcesMatchingScopeFilter returns an iterator over the cached items whose scope matches the given
|
||||
// primary scope filter, selecting the appropriate traversal strategy for the filter's mode. This is the
|
||||
// cache-side counterpart to [scopes.MatchScope]: it must select exactly the set of items that MatchScope
|
||||
// would accept for the same filter, so that the indexed traversal and a naive per-item match agree.
|
||||
//
|
||||
// An empty/unspecified filter and MODE_ALL are both treated as "match everything" (the scoped equivalent
|
||||
// of a get-all), consistent with MatchScope's wildcard handling. The filter is validated via [scopes.ValidateFilter]
|
||||
// before use, so callers do not need to pre-validate.
|
||||
//
|
||||
// NOTE: currently MODE_UNSCOPED is treated as a valid input that happens to match nothing, which has been
|
||||
// judged to be the most consistent behavior. Especially if we decide to start supporting unscoped resources
|
||||
// in this cache in the future.
|
||||
func (c *Cache[T, K]) ResourcesMatchingScopeFilter(filter *scopesv1.Filter, opts ...Option[T, K]) (iter.Seq[ScopedItems[T]], error) {
|
||||
if err := scopes.ValidateFilter(filter); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
scope := filter.GetScope()
|
||||
switch filter.GetMode() {
|
||||
case scopesv1.Mode_MODE_UNSPECIFIED, scopesv1.Mode_MODE_ALL:
|
||||
// wildcard: yield everything (an exhaustive iteration from root).
|
||||
return c.ScopeAndDescendants(scopes.Root, opts...), nil
|
||||
case scopesv1.Mode_MODE_EXACT:
|
||||
return c.ExactScope(scope, opts...), nil
|
||||
case scopesv1.Mode_MODE_DESCENDANTS:
|
||||
return c.ScopeAndDescendants(scope, opts...), nil
|
||||
case scopesv1.Mode_MODE_ANCESTORS, scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE: //nolint:staticcheck // SA1019. Deprecated mode retained as equivalent for backwards compatibility.
|
||||
return c.ScopeAndAncestors(scope, opts...), nil
|
||||
case scopesv1.Mode_MODE_RELATIVES:
|
||||
return c.ScopeAndRelatives(scope, opts...), nil
|
||||
case scopesv1.Mode_MODE_UNSCOPED:
|
||||
// scoped caches only contain scoped resources, so an unscoped filter matches nothing.
|
||||
return func(yield func(ScopedItems[T]) bool) {}, nil
|
||||
default:
|
||||
return nil, trace.BadParameter("unsupported scope filter mode %v (this is a bug)", filter.GetMode())
|
||||
}
|
||||
}
|
||||
|
||||
// ExactScope iterates over the cached items at exactly the given scope, with no ancestors or descendants.
|
||||
// This corresponds to scopesv1.Mode_MODE_EXACT.
|
||||
func (c *Cache[T, K]) ExactScope(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
return func(yield func(ScopedItems[T]) bool) {
|
||||
c.rw.RLock()
|
||||
defer c.rw.RUnlock()
|
||||
|
||||
var options options[T, K]
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
if c.root == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// keep track of the segments visited
|
||||
var visited []string
|
||||
|
||||
// search for start position, beginning at the root
|
||||
current := c.root
|
||||
|
||||
descender := newDescender(options.cursor)
|
||||
defer descender.Stop()
|
||||
|
||||
for segment := range scopes.DescendingSegments(scope) {
|
||||
// get next scope if it exists
|
||||
var ok bool
|
||||
current, ok = current.children.Get(segment)
|
||||
if !ok {
|
||||
// the target scope does not exist, nothing to yield.
|
||||
return
|
||||
}
|
||||
|
||||
// advance the descender to the next scope
|
||||
descender.Descend(segment)
|
||||
|
||||
// update visited segments
|
||||
visited = append(visited, segment)
|
||||
}
|
||||
|
||||
// yield only the members at exactly the target scope (no recursion into descendants).
|
||||
if !maybeYieldScopedItems(yield, visited, &descender, current.members, c.items, c.cfg.Clone, options.filter) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ScopeAndAncestors iterates over the cached items at the given scope and all of its ancestors (i.e.
|
||||
// a descending iteration from root, through the leaf of the specified scope). This corresponds to
|
||||
// scopesv1.Mode_MODE_ANCESTORS.
|
||||
func (c *Cache[T, K]) ScopeAndAncestors(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
return func(yield func(ScopedItems[T]) bool) {
|
||||
c.rw.RLock()
|
||||
defer c.rw.RUnlock()
|
||||
@@ -212,9 +300,10 @@ func (c *Cache[T, K]) PoliciesApplicableToResourceScope(scope string, opts ...Op
|
||||
}
|
||||
}
|
||||
|
||||
// ResourcesSubjectToPolicyScope iterates over the cached items using resources-subjugation rules (i.e.
|
||||
// an exhaustive descending iteration of the specified scope and all of its descendants).
|
||||
func (c *Cache[T, K]) ResourcesSubjectToPolicyScope(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
// ScopeAndDescendants iterates over the cached items at the given scope and all of its descendants (i.e.
|
||||
// an exhaustive descending iteration of the specified scope and all of its descendants). This corresponds
|
||||
// to scopesv1.Mode_MODE_DESCENDANTS.
|
||||
func (c *Cache[T, K]) ScopeAndDescendants(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
return func(yield func(ScopedItems[T]) bool) {
|
||||
c.rw.RLock()
|
||||
defer c.rw.RUnlock()
|
||||
@@ -261,13 +350,13 @@ func (c *Cache[T, K]) ResourcesSubjectToPolicyScope(scope string, opts ...Option
|
||||
}
|
||||
}
|
||||
|
||||
// AllNonOrthogonalResources returns all resources at scopes non-orhthogonal to the given scope. For example, given the
|
||||
// ScopeAndRelatives returns all resources at scopes non-orhthogonal to the given scope. For example, given the
|
||||
// scope '/foo', this will return resources at '/', '/foo', '/foo/bar', etc, but not '/bar'. This operation is less typical
|
||||
// than either PoliciesApplicableToResourceScope or ResourcesSubjectToPolicyScope. Currently, the only operation that wants
|
||||
// than either ScopeAndAncestors or ScopeAndDescendants. Currently, the only operation that wants
|
||||
// this kind of query is scope pin generation, which must discover all policies that might apply to access at the pinned scope
|
||||
// *or* any of its sub-scopes. This can reasonably be thought of as producing the union of the result of both the
|
||||
// PoliciesApplicableToResourceScope and ResourcesSubjectToPolicyScope methods.
|
||||
func (c *Cache[T, K]) AllNonOrthogonalResources(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
// ScopeAndAncestors and ScopeAndDescendants methods, and corresponds to scopesv1.Mode_MODE_RELATIVES.
|
||||
func (c *Cache[T, K]) ScopeAndRelatives(scope string, opts ...Option[T, K]) iter.Seq[ScopedItems[T]] {
|
||||
return func(yield func(ScopedItems[T]) bool) {
|
||||
c.rw.RLock()
|
||||
defer c.rw.RUnlock()
|
||||
|
||||
Vendored
+45
-45
@@ -222,7 +222,7 @@ func TestCacheFiltering(t *testing.T) {
|
||||
}
|
||||
|
||||
var policiesApplicableTo []item[int]
|
||||
for scope := range cache.PoliciesApplicableToResourceScope(tt.scope, cache.WithFilter(tt.filter)) {
|
||||
for scope := range cache.ScopeAndAncestors(tt.scope, cache.WithFilter(tt.filter)) {
|
||||
var seen int
|
||||
for item := range scope.Items() {
|
||||
seen++
|
||||
@@ -238,7 +238,7 @@ func TestCacheFiltering(t *testing.T) {
|
||||
|
||||
cloned = 0
|
||||
var resourcesSubjectTo []item[int]
|
||||
for scope := range cache.ResourcesSubjectToPolicyScope(tt.scope, cache.WithFilter(tt.filter)) {
|
||||
for scope := range cache.ScopeAndDescendants(tt.scope, cache.WithFilter(tt.filter)) {
|
||||
var seen int
|
||||
for item := range scope.Items() {
|
||||
seen++
|
||||
@@ -254,7 +254,7 @@ func TestCacheFiltering(t *testing.T) {
|
||||
|
||||
cloned = 0
|
||||
var nonOrthogonalResources []item[int]
|
||||
for scope := range cache.AllNonOrthogonalResources(tt.scope, cache.WithFilter(tt.filter)) {
|
||||
for scope := range cache.ScopeAndRelatives(tt.scope, cache.WithFilter(tt.filter)) {
|
||||
var seen int
|
||||
for item := range scope.Items() {
|
||||
seen++
|
||||
@@ -275,10 +275,10 @@ func TestCacheFiltering(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheAllNonOrthogonalResources verifies the expected behavior of the AllNonOrthogonalResources method separately
|
||||
// TestCacheScopeAndRelatives verifies the expected behavior of the ScopeAndRelatives method separately
|
||||
// because most coverage is provided by asserting that it produces the union of the output of the other two
|
||||
// methods. This test primarily servers as a sanity-check to ensure that the other checks aren't wildly off-base.
|
||||
func TestCacheAllNonOrthogonalResources(t *testing.T) {
|
||||
func TestCacheScopeAndRelatives(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
items := []item[int]{
|
||||
@@ -355,7 +355,7 @@ func TestCacheAllNonOrthogonalResources(t *testing.T) {
|
||||
}
|
||||
|
||||
var nonOrthogonalResources []item[int]
|
||||
for scope := range cache.AllNonOrthogonalResources(tt.scope) {
|
||||
for scope := range cache.ScopeAndRelatives(tt.scope) {
|
||||
for item := range scope.Items() {
|
||||
nonOrthogonalResources = append(nonOrthogonalResources, item)
|
||||
}
|
||||
@@ -366,8 +366,8 @@ func TestCacheAllNonOrthogonalResources(t *testing.T) {
|
||||
}
|
||||
|
||||
// joinAndDeduplicateItems joins multiple sets of items and deduplicates them based on their keys. Used in testing
|
||||
// the AllNonOrthogonalResources method since its output should always be the union of the output of
|
||||
// PoliciesApplicableToResourceScope and ResourcesSubjectToPolicyScope.
|
||||
// the ScopeAndRelatives method since its output should always be the union of the output of
|
||||
// ScopeAndAncestors and ScopeAndDescendants.
|
||||
func joinAndDeduplicateItems(sets ...[]item[int]) []item[int] {
|
||||
// join all sets together and deduplicate
|
||||
seen := make(map[int]struct{})
|
||||
@@ -419,7 +419,7 @@ func TestCacheConcurrency(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
// perform a policy application query that will match all items
|
||||
for _ = range cache.PoliciesApplicableToResourceScope("/aa/bb/cc") {
|
||||
for _ = range cache.ScopeAndAncestors("/aa/bb/cc") {
|
||||
lockstepC <- struct{}{} // block until the second query has caught up
|
||||
<-proceedC // wait for the main test routine to unblock us
|
||||
}
|
||||
@@ -427,7 +427,7 @@ func TestCacheConcurrency(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
// perform a resource subjugation query that will match all items
|
||||
for _ = range cache.ResourcesSubjectToPolicyScope("/") {
|
||||
for _ = range cache.ScopeAndDescendants("/") {
|
||||
<-lockstepC // wait until we get the signal from the first query
|
||||
}
|
||||
}()
|
||||
@@ -784,7 +784,7 @@ func TestCursorScenarios(t *testing.T) {
|
||||
|
||||
// verify policies-applicable-to-resource iteration
|
||||
var policiesApplicableTo []item[int]
|
||||
for scope := range cache.PoliciesApplicableToResourceScope(tt.scope, cache.WithCursor(tt.cursor)) {
|
||||
for scope := range cache.ScopeAndAncestors(tt.scope, cache.WithCursor(tt.cursor)) {
|
||||
for item := range scope.Items() {
|
||||
policiesApplicableTo = append(policiesApplicableTo, item)
|
||||
}
|
||||
@@ -794,7 +794,7 @@ func TestCursorScenarios(t *testing.T) {
|
||||
|
||||
// verify resources-subject-to-policy iteration
|
||||
var resourcesSubjectTo []item[int]
|
||||
for scope := range cache.ResourcesSubjectToPolicyScope(tt.scope, cache.WithCursor(tt.cursor)) {
|
||||
for scope := range cache.ScopeAndDescendants(tt.scope, cache.WithCursor(tt.cursor)) {
|
||||
for item := range scope.Items() {
|
||||
resourcesSubjectTo = append(resourcesSubjectTo, item)
|
||||
}
|
||||
@@ -804,7 +804,7 @@ func TestCursorScenarios(t *testing.T) {
|
||||
|
||||
// verify non-orhthogonal iteration
|
||||
var nonOrthogonal []item[int]
|
||||
for scope := range cache.AllNonOrthogonalResources(tt.scope, cache.WithCursor(tt.cursor)) {
|
||||
for scope := range cache.ScopeAndRelatives(tt.scope, cache.WithCursor(tt.cursor)) {
|
||||
for item := range scope.Items() {
|
||||
nonOrthogonal = append(nonOrthogonal, item)
|
||||
}
|
||||
@@ -973,7 +973,7 @@ func TestCursorPagination(t *testing.T) {
|
||||
var remaining []item[int]
|
||||
var cursor Cursor[int]
|
||||
PolicyApplicationOuter:
|
||||
for scope := range cache.PoliciesApplicableToResourceScope(tt.scope) {
|
||||
for scope := range cache.ScopeAndAncestors(tt.scope) {
|
||||
for item := range scope.Items() {
|
||||
if len(firstPage) == tt.limit {
|
||||
cursor = Cursor[int]{
|
||||
@@ -989,7 +989,7 @@ func TestCursorPagination(t *testing.T) {
|
||||
require.Equal(t, tt.policiesApplicableTo.firstPage, firstPage)
|
||||
|
||||
if !cursor.IsZero() {
|
||||
for scope := range cache.PoliciesApplicableToResourceScope(tt.scope, cache.WithCursor(cursor)) {
|
||||
for scope := range cache.ScopeAndAncestors(tt.scope, cache.WithCursor(cursor)) {
|
||||
for item := range scope.Items() {
|
||||
remaining = append(remaining, item)
|
||||
}
|
||||
@@ -1005,7 +1005,7 @@ func TestCursorPagination(t *testing.T) {
|
||||
cursor = Cursor[int]{}
|
||||
|
||||
ResourceSubjugationOuter:
|
||||
for scope := range cache.ResourcesSubjectToPolicyScope(tt.scope) {
|
||||
for scope := range cache.ScopeAndDescendants(tt.scope) {
|
||||
for item := range scope.Items() {
|
||||
if len(firstPage) == tt.limit {
|
||||
cursor = Cursor[int]{
|
||||
@@ -1021,7 +1021,7 @@ func TestCursorPagination(t *testing.T) {
|
||||
require.Equal(t, tt.resourcesSubjectTo.firstPage, firstPage)
|
||||
|
||||
if !cursor.IsZero() {
|
||||
for scope := range cache.ResourcesSubjectToPolicyScope(tt.scope, cache.WithCursor(cursor)) {
|
||||
for scope := range cache.ScopeAndDescendants(tt.scope, cache.WithCursor(cursor)) {
|
||||
for item := range scope.Items() {
|
||||
remaining = append(remaining, item)
|
||||
}
|
||||
@@ -1073,12 +1073,12 @@ func TestCacheOperations(t *testing.T) {
|
||||
require.Equal(t, 0, cache.Len())
|
||||
|
||||
// verify empty reads of root
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/")))
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.ScopeAndAncestors("/")))
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify empty sub-scope reads
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child")))
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/child")))
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.ScopeAndAncestors("/child")))
|
||||
require.Equal(t, map[string][]string{}, collectScopedItemKeys(cache.ScopeAndDescendants("/child")))
|
||||
|
||||
for _, item := range items {
|
||||
cache.Put(item)
|
||||
@@ -1090,49 +1090,49 @@ func TestCacheOperations(t *testing.T) {
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped", "root-scoped-other"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped", "root-scoped-other"},
|
||||
"/child": {"child-scoped", "child-scoped-other"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped", "root-scoped-other"},
|
||||
"/child": {"child-scoped", "child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
// verify basic resources-subject-to-policy iteration
|
||||
|
||||
require.Equal(t, map[string][]string{},
|
||||
collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/nonexistent")))
|
||||
collectScopedItemKeys(cache.ScopeAndDescendants("/nonexistent")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/child/subchild")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child": {"child-scoped", "child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/child")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/child")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped", "root-scoped-other"},
|
||||
"/child": {"child-scoped", "child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify that the concept of policies-applicable-to-resource used by the cache
|
||||
// matches up with the definition expected by the scopes package.
|
||||
for scope := range cache.PoliciesApplicableToResourceScope("/child/subchild") {
|
||||
for scope := range cache.ScopeAndAncestors("/child/subchild") {
|
||||
require.True(t, scopes.ResourceScope("/child/subchild").IsSubjectToScopeOfEffect(scope.Scope()), "scope=%s", scope.Scope())
|
||||
}
|
||||
|
||||
// verify that the concept of resources-subject-to-policy used by the cache
|
||||
// matches up with the definition expected by the scopes package.
|
||||
for scope := range cache.ResourcesSubjectToPolicyScope("/child") {
|
||||
for scope := range cache.ScopeAndDescendants("/child") {
|
||||
require.True(t, scopes.ScopeOfEffect("/child").AppliesToResourceScope(scope.Scope()), "scope=%s", scope.Scope())
|
||||
}
|
||||
|
||||
@@ -1144,14 +1144,14 @@ func TestCacheOperations(t *testing.T) {
|
||||
"/": {"root-scoped", "root-scoped-other"},
|
||||
"/child": {"child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped", "root-scoped-other"},
|
||||
"/child": {"child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify deletion of a single root-scoped item
|
||||
cache.Del("root-scoped")
|
||||
@@ -1161,14 +1161,14 @@ func TestCacheOperations(t *testing.T) {
|
||||
"/": {"root-scoped-other"},
|
||||
"/child": {"child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped-other"},
|
||||
"/child": {"child-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify full deletion of all contents of an intermediate scope
|
||||
cache.Del("child-scoped-other")
|
||||
@@ -1177,13 +1177,13 @@ func TestCacheOperations(t *testing.T) {
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/": {"root-scoped-other"},
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verfiy full deletion of all contents of a root scope
|
||||
cache.Del("root-scoped-other")
|
||||
@@ -1191,23 +1191,23 @@ func TestCacheOperations(t *testing.T) {
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child/subchild": {"child-sub-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify deletion of leaf scope
|
||||
cache.Del("child-sub-scoped")
|
||||
require.Equal(t, len(items)-5, cache.Len())
|
||||
|
||||
require.Equal(t, map[string][]string{},
|
||||
collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify basic re-add
|
||||
cache.Put(item[string]{
|
||||
@@ -1218,12 +1218,12 @@ func TestCacheOperations(t *testing.T) {
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child": {"child-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child": {"child-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
|
||||
// verify overwrite of existing item by primary key
|
||||
cache.Put(item[string]{
|
||||
@@ -1233,16 +1233,16 @@ func TestCacheOperations(t *testing.T) {
|
||||
require.Equal(t, len(items)-4, cache.Len())
|
||||
|
||||
require.Equal(t, map[string][]string{},
|
||||
collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/subchild")))
|
||||
collectScopedItemKeys(cache.ScopeAndAncestors("/child/subchild")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child/other": {"child-scoped"},
|
||||
}, collectScopedItemKeys(cache.PoliciesApplicableToResourceScope("/child/other")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndAncestors("/child/other")))
|
||||
|
||||
require.Equal(t, map[string][]string{
|
||||
"/child/other": {"child-scoped"},
|
||||
"/orthogonal": {"child-orthogonal"},
|
||||
}, collectScopedItemKeys(cache.ResourcesSubjectToPolicyScope("/")))
|
||||
}, collectScopedItemKeys(cache.ScopeAndDescendants("/")))
|
||||
}
|
||||
|
||||
// collectScopedItemKeys aggregates a scoped iterator from one of the cache iteration
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
)
|
||||
|
||||
// newFilterMatchCache returns a cache pre-populated with items that exercise the filter-matching logic.
|
||||
func newFilterMatchCache(t *testing.T) *Cache[item[int], int] {
|
||||
t.Helper()
|
||||
c, err := New(Config[item[int], int]{
|
||||
Scope: (item[int]).Scope,
|
||||
Key: (item[int]).Key,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
filterMatchItems := []item[int]{
|
||||
{1, "/"},
|
||||
{2, "/aa"},
|
||||
{3, "/aa/bb"},
|
||||
{4, "/aa/bb/cc"},
|
||||
{5, "/aa/cc"},
|
||||
}
|
||||
|
||||
for _, it := range filterMatchItems {
|
||||
c.Put(it)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// TestCacheExactScope verifies that ExactScope yields only the members at exactly the given scope.
|
||||
func TestCacheExactScope(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := newFilterMatchCache(t)
|
||||
|
||||
tests := []struct {
|
||||
scope string
|
||||
want map[string][]int
|
||||
}{
|
||||
{scope: "/aa/bb", want: map[string][]int{"/aa/bb": {3}}},
|
||||
{scope: "/", want: map[string][]int{"/": {1}}},
|
||||
{scope: "/aa/bb/cc", want: map[string][]int{"/aa/bb/cc": {4}}},
|
||||
{scope: "/nonexistent", want: map[string][]int{}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.scope, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, collectScopedItemKeys(c.ExactScope(tt.scope)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestResourcesMatchingScopeFilter verifies that the primary-filter mode is mapped to the correct cache
|
||||
// traversal, including the wildcard (nil/unspecified/all) and unscoped (matches-nothing) cases.
|
||||
func TestResourcesMatchingScopeFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := newFilterMatchCache(t)
|
||||
|
||||
const filterScope = "/aa/bb"
|
||||
all := map[string][]int{"/": {1}, "/aa": {2}, "/aa/bb": {3}, "/aa/bb/cc": {4}, "/aa/cc": {5}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filter *scopesv1.Filter
|
||||
want map[string][]int
|
||||
}{
|
||||
{
|
||||
name: "nil is wildcard",
|
||||
filter: nil,
|
||||
want: all,
|
||||
},
|
||||
{
|
||||
name: "unspecified is wildcard",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSPECIFIED}.Build(),
|
||||
want: all,
|
||||
},
|
||||
{
|
||||
name: "all is wildcard",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
want: all,
|
||||
},
|
||||
{
|
||||
name: "exact",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT, Scope: filterScope}.Build(),
|
||||
want: map[string][]int{"/aa/bb": {3}},
|
||||
},
|
||||
{
|
||||
name: "descendants",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_DESCENDANTS, Scope: filterScope}.Build(),
|
||||
want: map[string][]int{"/aa/bb": {3}, "/aa/bb/cc": {4}},
|
||||
},
|
||||
{
|
||||
name: "ancestors",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ANCESTORS, Scope: filterScope}.Build(),
|
||||
want: map[string][]int{"/": {1}, "/aa": {2}, "/aa/bb": {3}},
|
||||
},
|
||||
{
|
||||
name: "relatives",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_RELATIVES, Scope: filterScope}.Build(),
|
||||
want: map[string][]int{"/": {1}, "/aa": {2}, "/aa/bb": {3}, "/aa/bb/cc": {4}},
|
||||
},
|
||||
{
|
||||
name: "unscoped matches nothing",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSCOPED}.Build(),
|
||||
want: map[string][]int{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
getter, err := c.ResourcesMatchingScopeFilter(tt.filter)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, collectScopedItemKeys(getter))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestResourcesMatchingScopeFilterInvalid verifies that a malformed filter is rejected rather than silently
|
||||
// mishandled.
|
||||
func TestResourcesMatchingScopeFilterInvalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := newFilterMatchCache(t)
|
||||
|
||||
// EXACT requires a non-empty scope.
|
||||
_, err := c.ResourcesMatchingScopeFilter(scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT}.Build())
|
||||
require.Error(t, err)
|
||||
|
||||
// UNSCOPED requires an empty scope.
|
||||
_, err = c.ResourcesMatchingScopeFilter(scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSCOPED, Scope: "/aa"}.Build())
|
||||
require.Error(t, err)
|
||||
}
|
||||
Vendored
+7
-35
@@ -26,8 +26,6 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopespb "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
"github.com/gravitational/teleport/lib/scopes/cache"
|
||||
)
|
||||
@@ -89,43 +87,11 @@ func (c *RoleCache) ListScopedRolesWithFilter(ctx context.Context, req *scopedac
|
||||
pageSize = maxPageSize
|
||||
}
|
||||
|
||||
if req.GetResourceScope() != nil && req.GetAssignableScope() != nil {
|
||||
return nil, trace.BadParameter("cannot filter by both resource scope and assignable scope simultaneously")
|
||||
}
|
||||
|
||||
cursor, err := cache.DecodeStringCursor(req.GetPageToken())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// resources subject to policy scope root is basically the scoped resource equivalent
|
||||
// of a "get all". this is a reasonable default for most queries.
|
||||
getter := c.cache.ResourcesSubjectToPolicyScope
|
||||
scope := scopes.Root
|
||||
|
||||
// if a resource scope filter has been provided, the user has specified a custom scope/mode to
|
||||
// query by.
|
||||
if req.GetResourceScope() != nil {
|
||||
// a resource-scope based filter has been provided
|
||||
switch req.GetResourceScope().GetMode() {
|
||||
case scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE:
|
||||
getter = c.cache.ResourcesSubjectToPolicyScope
|
||||
case scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE:
|
||||
getter = c.cache.PoliciesApplicableToResourceScope
|
||||
default:
|
||||
return nil, trace.BadParameter("unsupported or unspecified scoping mode %q in scoped role resource scope filter", req.GetResourceScope().GetMode())
|
||||
}
|
||||
scope = req.GetResourceScope().GetScope()
|
||||
}
|
||||
|
||||
if req.GetAssignableScope() != nil {
|
||||
// NOTE: we eventually want to be able to query roles by the scopes at which they are assignable,
|
||||
// instead of just resource scope. this is important for introspection/auditing but not necessary for
|
||||
// the core funcationality of teleport, so implementation has been deferred. (will require implementation
|
||||
// of a more complex caching structure where each resource will be associable with multiple scopes)
|
||||
return nil, trace.NotImplemented("assignable scope filtering for scoped role roles is not yet supported")
|
||||
}
|
||||
|
||||
finalFilter := filter
|
||||
if nameFilter := req.GetNameFilter(); nameFilter != "" {
|
||||
finalFilter = func(role *scopedaccessv1.ScopedRole) bool {
|
||||
@@ -133,10 +99,16 @@ func (c *RoleCache) ListScopedRolesWithFilter(ctx context.Context, req *scopedac
|
||||
}
|
||||
}
|
||||
|
||||
// the primary scope filter selects the cache traversal strategy.
|
||||
resources, err := c.cache.ResourcesMatchingScopeFilter(req.GetScopeFilter(), c.cache.WithCursor(cursor), c.cache.WithFilter(finalFilter))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
var out []*scopedaccessv1.ScopedRole
|
||||
var nextCursor cache.Cursor[string]
|
||||
Outer:
|
||||
for scope := range getter(scope, c.cache.WithCursor(cursor), c.cache.WithFilter(finalFilter)) {
|
||||
for scope := range resources {
|
||||
for role := range scope.Items() {
|
||||
if len(out) == pageSize {
|
||||
nextCursor = cache.Cursor[string]{
|
||||
|
||||
+24
-24
@@ -121,8 +121,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
|
||||
// verify expected behavior for standard cursors in resources subject to scope mode
|
||||
rsp, err := cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:user-02@/aa",
|
||||
@@ -133,8 +133,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
|
||||
// try to inject a malicious root out-of-band cursor in resources subject to scope mode (no effect)
|
||||
rsp, err = cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:user-01@/",
|
||||
@@ -145,8 +145,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
|
||||
// try to inject a malicious orthogonal out-of-band cursor in resources subject to scope mode (no effect)
|
||||
rsp, err = cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:intern-01@/bb",
|
||||
@@ -157,8 +157,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
|
||||
// verify expected behavior for standard cursors in policies applicable to scope mode
|
||||
rsp, err = cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:user-01@/",
|
||||
@@ -170,8 +170,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
// try to inject a malicious child out-of-band cursor in policies applicable to scope mode (effect is
|
||||
// to ignore all items in valid query path).
|
||||
rsp, err = cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:user-03@/aa/bb",
|
||||
@@ -183,8 +183,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
// try to inject a malicious orthogonal out-of-band cursor in policies applicable to scope mode (effect is to
|
||||
// ignore root, but process leaf normally).
|
||||
rsp, err = cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v1:intern-01@/bb",
|
||||
@@ -195,8 +195,8 @@ func TestListScopedRolesScenarios(t *testing.T) {
|
||||
|
||||
// verify rejection of unknown cursor version
|
||||
_, err = cache.ListScopedRoles(t.Context(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
PageToken: "v2:user-02@/aa",
|
||||
@@ -327,8 +327,8 @@ func TestListScopedRolesBasics(t *testing.T) {
|
||||
{
|
||||
name: "resource scope root",
|
||||
req: scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -345,8 +345,8 @@ func TestListScopedRolesBasics(t *testing.T) {
|
||||
{
|
||||
name: "resource scope non-root",
|
||||
req: scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -360,8 +360,8 @@ func TestListScopedRolesBasics(t *testing.T) {
|
||||
{
|
||||
name: "policy scope root",
|
||||
req: scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -375,8 +375,8 @@ func TestListScopedRolesBasics(t *testing.T) {
|
||||
{
|
||||
name: "policy scope non-root",
|
||||
req: scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/aa",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
@@ -428,8 +428,8 @@ func TestListScopedRolesBasics(t *testing.T) {
|
||||
name: "name filter policy scope root",
|
||||
req: scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
NameFilter: "01",
|
||||
ResourceScope: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopespb.Filter_builder{
|
||||
Mode: scopespb.Mode_MODE_ANCESTORS,
|
||||
Scope: "/",
|
||||
}.Build(),
|
||||
}.Build(),
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
)
|
||||
|
||||
// IsMatchAll reports whether the given filter is a wildcard match that selects all resources.
|
||||
func IsMatchAll(filter *scopesv1.Filter) bool {
|
||||
// an unspecified filter and MODE_ALL are both treated as a wildcard match at the matching layer. see
|
||||
// [MatchScope] for discussion of why these are equivalent here despite being authorized differently
|
||||
// at the API/authz layer.
|
||||
mode := filter.GetMode()
|
||||
return mode == scopesv1.Mode_MODE_UNSPECIFIED || mode == scopesv1.Mode_MODE_ALL
|
||||
}
|
||||
|
||||
// MatchScope reports whether a resource at the given scope matches the supplied filter. This is the
|
||||
// authoritative scope-matching logic used by caches/backends to decide which resources a filter selects.
|
||||
//
|
||||
// The empty string ("") is used to represent an unscoped resource and is treated as orthogonal to all
|
||||
// scoped values (it is *not* equivalent to the root scope). As a result, the relationship-based modes
|
||||
// never match unscoped resources, and MODE_UNSCOPED matches only unscoped resources.
|
||||
//
|
||||
// Matching logic here treats empty/unspecified filters and MODE_ALL as equivalent wildcard matchers. However,
|
||||
// the API/authz defaults an empty/unspecified filter to being one of UNSCOPED or EXACT depending on the
|
||||
// caller's identity. This helps ensure that outdated or un-explicit calls result in safe/conservative defaults.
|
||||
//
|
||||
// This function does not validate its inputs. Use [ValidateFilter] to verify that a filter is well-formed
|
||||
// (e.g. that the scope and mode are mutually consistent) before relying on it.
|
||||
func MatchScope(filter *scopesv1.Filter, resourceScope string) bool {
|
||||
if IsMatchAll(filter) {
|
||||
return true
|
||||
}
|
||||
|
||||
mode := filter.GetMode()
|
||||
|
||||
if mode == scopesv1.Mode_MODE_UNSCOPED {
|
||||
// unscoped resources only.
|
||||
return resourceScope == ""
|
||||
}
|
||||
|
||||
// all remaining modes select resources by the relationship of the resource scope to the filter scope.
|
||||
rel := Compare(filter.GetScope(), resourceScope)
|
||||
switch mode {
|
||||
case scopesv1.Mode_MODE_EXACT:
|
||||
return rel == Equivalent
|
||||
case scopesv1.Mode_MODE_DESCENDANTS:
|
||||
return rel == Equivalent || rel == Descendant
|
||||
case scopesv1.Mode_MODE_ANCESTORS, scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE: //nolint:staticcheck // SA1019. Deprecated mode retained as equivalent for backwards compatibility.
|
||||
return rel == Equivalent || rel == Ancestor
|
||||
case scopesv1.Mode_MODE_RELATIVES:
|
||||
return rel != Orthogonal
|
||||
default:
|
||||
// unknown modes match nothing.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateFilter checks that a filter is well-formed: that its mode is recognized and that its scope is
|
||||
// consistent with its mode. Relational modes (EXACT/DESCENDANTS/ANCESTORS/RELATIVES) require a
|
||||
// non-empty/valid scope, while the non-relational modes (UNSCOPED/ALL) and the unspecified mode
|
||||
// require an empty scope. A nil or unspecified filter is considered valid.
|
||||
func ValidateFilter(filter *scopesv1.Filter) error {
|
||||
switch filter.GetMode() {
|
||||
case scopesv1.Mode_MODE_UNSPECIFIED:
|
||||
if filter.GetScope() != "" {
|
||||
return trace.BadParameter("scope filter specifies a scope %q without a mode", filter.GetScope())
|
||||
}
|
||||
return nil
|
||||
case scopesv1.Mode_MODE_EXACT,
|
||||
scopesv1.Mode_MODE_DESCENDANTS,
|
||||
scopesv1.Mode_MODE_ANCESTORS,
|
||||
scopesv1.Mode_MODE_RELATIVES,
|
||||
scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE: //nolint:staticcheck // SA1019. Deprecated mode retained as equivalent for backwards compatibility.
|
||||
if filter.GetScope() == "" {
|
||||
return trace.BadParameter("scope filter mode %v requires a non-empty scope", filter.GetMode())
|
||||
}
|
||||
if err := WeakValidate(filter.GetScope()); err != nil {
|
||||
return trace.Wrap(err, "invalid scope in scope filter")
|
||||
}
|
||||
return nil
|
||||
case scopesv1.Mode_MODE_UNSCOPED, scopesv1.Mode_MODE_ALL:
|
||||
if filter.GetScope() != "" {
|
||||
return trace.BadParameter("scope filter mode %v requires an empty scope, got %q", filter.GetMode(), filter.GetScope())
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return trace.BadParameter("unknown scope filter mode %v", filter.GetMode())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Teleport
|
||||
* Copyright (C) 2026 Gravitational, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
)
|
||||
|
||||
// TestMatchScope verifies the core scope-matching logic across all modes and scope relationships,
|
||||
// including the unscoped ("") resource scope and the wildcard (nil/unspecified/all) cases.
|
||||
func TestMatchScope(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// resourceScopes exercises the full set of relationships relative to a filter scope of "/aa/bb":
|
||||
// the exact scope, an ancestor, the root ancestor, a descendant, an orthogonal scope, and unscoped.
|
||||
const filterScope = "/aa/bb"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mode scopesv1.Mode
|
||||
// want maps a resource scope to whether it should match.
|
||||
want map[string]bool
|
||||
}{
|
||||
{
|
||||
name: "exact",
|
||||
mode: scopesv1.Mode_MODE_EXACT,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": true,
|
||||
"/aa": false,
|
||||
"/": false,
|
||||
"/aa/bb/cc": false,
|
||||
"/aa/cc": false,
|
||||
"/xx": false,
|
||||
"": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "descendants",
|
||||
mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": true,
|
||||
"/aa": false,
|
||||
"/": false,
|
||||
"/aa/bb/cc": true,
|
||||
"/aa/cc": false,
|
||||
"/xx": false,
|
||||
"": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ancestors",
|
||||
mode: scopesv1.Mode_MODE_ANCESTORS,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": true,
|
||||
"/aa": true,
|
||||
"/": true,
|
||||
"/aa/bb/cc": false,
|
||||
"/aa/cc": false,
|
||||
"/xx": false,
|
||||
"": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relatives",
|
||||
mode: scopesv1.Mode_MODE_RELATIVES,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": true,
|
||||
"/aa": true,
|
||||
"/": true,
|
||||
"/aa/bb/cc": true,
|
||||
"/aa/cc": false,
|
||||
"/xx": false,
|
||||
"": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unscoped",
|
||||
mode: scopesv1.Mode_MODE_UNSCOPED,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": false,
|
||||
"/aa": false,
|
||||
"/": false,
|
||||
"/aa/bb/cc": false,
|
||||
"/aa/cc": false,
|
||||
"/xx": false,
|
||||
"": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all is a wildcard",
|
||||
mode: scopesv1.Mode_MODE_ALL,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": true,
|
||||
"/aa": true,
|
||||
"/": true,
|
||||
"/aa/bb/cc": true,
|
||||
"/aa/cc": true,
|
||||
"/xx": true,
|
||||
"": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unspecified is a wildcard",
|
||||
mode: scopesv1.Mode_MODE_UNSPECIFIED,
|
||||
want: map[string]bool{
|
||||
"/aa/bb": true,
|
||||
"/aa": true,
|
||||
"/": true,
|
||||
"/aa/bb/cc": true,
|
||||
"/aa/cc": true,
|
||||
"/xx": true,
|
||||
"": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filter := scopesv1.Filter_builder{
|
||||
Scope: filterScope,
|
||||
Mode: tt.mode,
|
||||
}.Build()
|
||||
|
||||
for resourceScope, want := range tt.want {
|
||||
require.Equal(t, want, MatchScope(filter, resourceScope), "resourceScope=%q", resourceScope)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchScopeNilFilter verifies that a nil filter is treated as a wildcard match.
|
||||
func TestMatchScopeNilFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, resourceScope := range []string{"", "/", "/aa", "/aa/bb"} {
|
||||
require.True(t, MatchScope(nil, resourceScope), "resourceScope=%q", resourceScope)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateFilter verifies basic filter validation.
|
||||
func TestValidateFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filter *scopesv1.Filter
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil filter is valid",
|
||||
filter: nil,
|
||||
},
|
||||
{
|
||||
name: "unspecified with empty scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSPECIFIED}.Build(),
|
||||
},
|
||||
{
|
||||
name: "unspecified with scope is invalid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSPECIFIED, Scope: "/aa"}.Build(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "exact with scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT, Scope: "/aa/bb"}.Build(),
|
||||
},
|
||||
{
|
||||
name: "exact without scope is invalid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT}.Build(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "descendants with scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_DESCENDANTS, Scope: "/aa"}.Build(),
|
||||
},
|
||||
{
|
||||
name: "ancestors with scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ANCESTORS, Scope: "/aa"}.Build(),
|
||||
},
|
||||
{
|
||||
name: "relatives with scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_RELATIVES, Scope: "/aa"}.Build(),
|
||||
},
|
||||
{
|
||||
name: "relationship mode with invalid scope is invalid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_EXACT, Scope: "/aa/b@b"}.Build(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unscoped with empty scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSCOPED}.Build(),
|
||||
},
|
||||
{
|
||||
name: "unscoped with scope is invalid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_UNSCOPED, Scope: "/aa"}.Build(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "all with empty scope is valid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
},
|
||||
{
|
||||
name: "all with scope is invalid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL, Scope: "/aa"}.Build(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unknown mode is invalid",
|
||||
filter: scopesv1.Filter_builder{Mode: scopesv1.Mode(999)}.Build(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateFilter(tt.filter)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -105,12 +105,8 @@ func (s *ScopedAccessService) GetScopedRole(ctx context.Context, req *scopedacce
|
||||
// ListScopedRoles returns a paginated list of scoped roles.
|
||||
// NOTE: this method is only used by local auth caches, and doesn't implement sorting, filtering, or pagination.
|
||||
func (s *ScopedAccessService) ListScopedRoles(ctx context.Context, req *scopedaccessv1.ListScopedRolesRequest) (*scopedaccessv1.ListScopedRolesResponse, error) {
|
||||
if req.GetResourceScope() != nil {
|
||||
return nil, trace.NotImplemented("filtering by resource scope is not implemented for direct backend scoped role reads")
|
||||
}
|
||||
|
||||
if req.GetAssignableScope() != nil {
|
||||
return nil, trace.NotImplemented("filtering by assignable scope is not implemented for direct backend scoped role reads")
|
||||
if err := scopes.ValidateFilter(req.GetScopeFilter()); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if req.GetNameFilter() != "" {
|
||||
@@ -127,6 +123,10 @@ func (s *ScopedAccessService) ListScopedRoles(ctx context.Context, req *scopedac
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !scopes.MatchScope(req.GetScopeFilter(), role.GetScope()) {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, role)
|
||||
}
|
||||
|
||||
@@ -375,12 +375,11 @@ func (s *ScopedAccessService) GetScopedRoleAssignment(ctx context.Context, req *
|
||||
// ListScopedRoleAssignments returns a paginated list of scoped role assignments.
|
||||
// NOTE: this method is only used by local auth caches, and doesn't implement sorting, filtering, or pagination.
|
||||
func (s *ScopedAccessService) ListScopedRoleAssignments(ctx context.Context, req *scopedaccessv1.ListScopedRoleAssignmentsRequest) (*scopedaccessv1.ListScopedRoleAssignmentsResponse, error) {
|
||||
if req.GetResourceScope() != nil {
|
||||
return nil, trace.NotImplemented("filtering by resource scope is not implemented for direct backend scoped role assignment reads")
|
||||
if err := scopes.ValidateFilter(req.GetScopeFilter()); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if req.GetAssignedScope() != nil {
|
||||
return nil, trace.NotImplemented("filtering by assigned scope is not implemented for direct backend scoped role assignment reads")
|
||||
if err := scopes.ValidateFilter(req.GetAssignedScopeFilter()); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if req.GetPageToken() != "" {
|
||||
@@ -392,6 +391,15 @@ func (s *ScopedAccessService) ListScopedRoleAssignments(ctx context.Context, req
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !scopes.MatchScope(req.GetScopeFilter(), assignment.GetScope()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !scopedaccess.MatchSecondaryAssignmentFilters(req, assignment) {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, assignment)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/retryutils"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
@@ -216,21 +215,6 @@ func (s *ScopedTokenService) UseScopedToken(ctx context.Context, token *joiningv
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func evalScopeFilter(filter *scopesv1.Filter, scope string) bool {
|
||||
if filter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch filter.GetMode() {
|
||||
case scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE:
|
||||
return scopes.ResourceScope(scope).IsSubjectToScopeOfEffect(filter.GetScope())
|
||||
case scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE:
|
||||
return scopes.ScopeOfEffect(scope).AppliesToResourceScope(filter.GetScope())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ListScopedTokens retrieves a paginated list of scoped join tokens.
|
||||
func (s *ScopedTokenService) ListScopedTokens(ctx context.Context, req *joiningv1.ListScopedTokensRequest) (*joiningv1.ListScopedTokensResponse, error) {
|
||||
// we only want to return filters if at least one of the filters
|
||||
@@ -238,8 +222,8 @@ func (s *ScopedTokenService) ListScopedTokens(ctx context.Context, req *joiningv
|
||||
// backend can choose to perform a simple list operation instead
|
||||
// of a list with filter
|
||||
switch {
|
||||
case req.HasResourceScope():
|
||||
case req.HasAssignedScope():
|
||||
case req.HasScopeFilter():
|
||||
case req.HasAssignedScopeFilter():
|
||||
case len(req.GetRoles()) > 0:
|
||||
case len(req.GetLabels()) > 0:
|
||||
default:
|
||||
@@ -257,17 +241,12 @@ func (s *ScopedTokenService) ListScopedTokens(ctx context.Context, req *joiningv
|
||||
}.Build(), nil
|
||||
}
|
||||
|
||||
if req.GetAssignedScope().GetScope() != "" {
|
||||
if err := scopes.WeakValidate(req.GetAssignedScope().GetScope()); err != nil {
|
||||
return nil, trace.BadParameter("invalid scope for assigned filter: %s", req.GetAssignedScope().GetScope())
|
||||
}
|
||||
|
||||
if err := scopes.ValidateFilter(req.GetAssignedScopeFilter()); err != nil {
|
||||
return nil, trace.Wrap(err, "invalid assigned scope filter")
|
||||
}
|
||||
|
||||
if req.GetResourceScope().GetScope() != "" {
|
||||
if err := scopes.WeakValidate(req.GetResourceScope().GetScope()); err != nil {
|
||||
return nil, trace.BadParameter("invalid scope for resource filter: %s", req.GetResourceScope().GetScope())
|
||||
}
|
||||
if err := scopes.ValidateFilter(req.GetScopeFilter()); err != nil {
|
||||
return nil, trace.Wrap(err, "invalid scope filter")
|
||||
}
|
||||
|
||||
filterRoles, err := types.NewTeleportRoles(req.GetRoles())
|
||||
@@ -287,11 +266,11 @@ func (s *ScopedTokenService) ListScopedTokens(ctx context.Context, req *joiningv
|
||||
}
|
||||
}
|
||||
|
||||
if !evalScopeFilter(req.GetAssignedScope(), token.GetSpec().GetAssignedScope()) {
|
||||
if !scopes.MatchScope(req.GetAssignedScopeFilter(), token.GetSpec().GetAssignedScope()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !evalScopeFilter(req.GetResourceScope(), token.GetScope()) {
|
||||
if !scopes.MatchScope(req.GetScopeFilter(), token.GetScope()) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -248,8 +248,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens assigning scope descendant of /test",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
AssignedScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/test",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -259,8 +259,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens assigning scope descendant of /test/aa",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
AssignedScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/test/aa",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -270,8 +270,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens assigning scope ancestor to /test/bb",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
AssignedScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_ANCESTORS,
|
||||
Scope: "/test/bb",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -281,8 +281,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens descendants of /test",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/test",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -292,8 +292,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens descendants of /test/aa",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/test/aa",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -303,8 +303,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens ancestor to /test/bb",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_POLICIES_APPLICABLE_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_ANCESTORS,
|
||||
Scope: "/test/bb",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -314,12 +314,12 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens descendant of /stage assigning /stage/aa",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/stage",
|
||||
}.Build(),
|
||||
AssignedScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/stage/aa",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -329,12 +329,12 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens descendant of /stage/aa assigning /stage/aa",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/stage/aa",
|
||||
}.Build(),
|
||||
AssignedScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
AssignedScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/stage/aa",
|
||||
}.Build(),
|
||||
WithSecrets: true,
|
||||
@@ -344,8 +344,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens in /test scope applying auth role",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/test",
|
||||
}.Build(),
|
||||
Roles: []string{types.RoleAuth.String()},
|
||||
@@ -356,8 +356,8 @@ func TestScopedTokenList(t *testing.T) {
|
||||
{
|
||||
name: "tokens in /test scope filtered by label",
|
||||
req: joiningv1.ListScopedTokensRequest_builder{
|
||||
ResourceScope: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_RESOURCES_SUBJECT_TO_SCOPE,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_DESCENDANTS,
|
||||
Scope: "/test",
|
||||
}.Build(),
|
||||
Roles: []string{types.RoleNode.String()},
|
||||
|
||||
@@ -305,6 +305,36 @@ func (c *ScopedAccessCheckerContext) riskyEnumerateScopedCheckers(ctx context.Co
|
||||
return c.enumerateAll(ctx)
|
||||
}
|
||||
|
||||
// ResolveScopeFilter returns the scope filter that should be used for a list/read/watch request, applying
|
||||
// identity-derived defaulting when the caller did not specify an explicit filter. An empty (nil or
|
||||
// MODE_UNSPECIFIED) filter is replaced with the safe default for this identity: MODE_EXACT at the pinned
|
||||
// scope for scoped identities, or MODE_UNSCOPED for unscoped identities. A filter with an explicit mode is
|
||||
// returned unchanged.
|
||||
//
|
||||
// This is the single source of truth for scope-filter defaulting. API handlers should pass caller-provided
|
||||
// filters through this method so that an omitted filter is consistently and safely defaulted, minimizing
|
||||
// per-API defaulting logic. Note that this method only handles defaulting; callers are still responsible for
|
||||
// validating (see [scopes.ValidateFilter]) and authorizing the resulting filter.
|
||||
func (c *ScopedAccessCheckerContext) ResolveScopeFilter(filter *scopesv1.Filter) *scopesv1.Filter {
|
||||
if filter.GetMode() != scopesv1.Mode_MODE_UNSPECIFIED {
|
||||
// the caller specified an explicit filter; return it unchanged.
|
||||
return filter
|
||||
}
|
||||
|
||||
if !c.isScoped() {
|
||||
// unscoped callers default to matching unscoped resources only.
|
||||
return scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_UNSCOPED,
|
||||
}.Build()
|
||||
}
|
||||
|
||||
// scoped callers default to the minimal/safe MODE_EXACT at their pinned scope.
|
||||
return scopesv1.Filter_builder{
|
||||
Scope: c.pin.GetScope(),
|
||||
Mode: scopesv1.Mode_MODE_EXACT,
|
||||
}.Build()
|
||||
}
|
||||
|
||||
// CheckMaybeHasAccessToRules returns an error if the context definitely does not have access to the provided
|
||||
// rules. For scoped identities, always returns nil — the scoped access model evaluates permissions per-resource.
|
||||
func (c *ScopedAccessCheckerContext) CheckMaybeHasAccessToRules(ctx RuleContext, resource string, verbs ...string) error {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/asciitable"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
@@ -150,7 +151,10 @@ func getScopedRole(ctx context.Context, client *authclient.Client, subKind strin
|
||||
return &scopedRoleCollection{roles: []*scopedaccessv1.ScopedRole{role}}, nil
|
||||
}
|
||||
|
||||
items, err := stream.Collect(scopedutils.RangeScopedRoles(ctx, client.ScopedAccessServiceClient(), &scopedaccessv1.ListScopedRolesRequest{}))
|
||||
items, err := stream.Collect(scopedutils.RangeScopedRoles(ctx, client.ScopedAccessServiceClient(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/asciitable"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
@@ -171,7 +172,10 @@ func getScopedRoleAssignment(ctx context.Context, client *authclient.Client, sub
|
||||
return &ScopedRoleAssignmentCollection{roleAssignments: []*scopedaccessv1.ScopedRoleAssignment{assignment}}, nil
|
||||
}
|
||||
|
||||
items, err := stream.Collect(scopedutils.RangeScopedRoleAssignments(ctx, client.ScopedAccessServiceClient(), &scopedaccessv1.ListScopedRoleAssignmentsRequest{}))
|
||||
items, err := stream.Collect(scopedutils.RangeScopedRoleAssignments(ctx, client.ScopedAccessServiceClient(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/clientutils"
|
||||
"github.com/gravitational/teleport/lib/asciitable"
|
||||
@@ -139,6 +140,8 @@ func getScopedToken(ctx context.Context, client *authclient.Client, subKind stri
|
||||
Limit: uint32(pageSize),
|
||||
Cursor: pageKey,
|
||||
WithSecrets: opts.WithSecrets,
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/itertools/stream"
|
||||
scopedutils "github.com/gravitational/teleport/lib/scopes/utils"
|
||||
@@ -104,6 +105,8 @@ func (c *scopedAssignmentsListCommand) list(ctx context.Context, client services
|
||||
items, err := stream.Collect(scopedutils.RangeScopedRoleAssignments(ctx, client, scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
User: c.user,
|
||||
Role: c.role,
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()))
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "collecting filtered scoped role assignments")
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/integration/helpers"
|
||||
"github.com/gravitational/teleport/lib/config"
|
||||
@@ -152,7 +153,10 @@ func TestScopedAssignmentListCommand(t *testing.T) {
|
||||
|
||||
ctx := t.Context()
|
||||
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
resp, err := scopedClt.ListScopedRoleAssignments(ctx, &scopedaccessv1.ListScopedRoleAssignmentsRequest{})
|
||||
resp, err := scopedClt.ListScopedRoleAssignments(ctx, scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
// exhaustive view: opt out of identity-based filter defaulting.
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.GetAssignments(), len(assignments))
|
||||
}, 10*time.Second, 50*time.Millisecond, "waiting for scoped role assignments to be present in cache")
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/lib/asciitable"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
@@ -118,7 +119,10 @@ func (c *scopedStatusCommand) status(ctx context.Context, client *authclient.Cli
|
||||
// aggregate per scope counts
|
||||
rows := make(map[string]scopeMetricRow)
|
||||
|
||||
for role, err := range scopedutils.RangeScopedRoles(ctx, client.ScopedAccessServiceClient(), &scopedaccessv1.ListScopedRolesRequest{}) {
|
||||
for role, err := range scopedutils.RangeScopedRoles(ctx, client.ScopedAccessServiceClient(), scopedaccessv1.ListScopedRolesRequest_builder{
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()) {
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -130,7 +134,10 @@ func (c *scopedStatusCommand) status(ctx context.Context, client *authclient.Cli
|
||||
rows[scope] = row
|
||||
}
|
||||
|
||||
for assignment, err := range scopedutils.RangeScopedRoleAssignments(ctx, client.ScopedAccessServiceClient(), &scopedaccessv1.ListScopedRoleAssignmentsRequest{}) {
|
||||
for assignment, err := range scopedutils.RangeScopedRoleAssignments(ctx, client.ScopedAccessServiceClient(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build()) {
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/gravitational/teleport"
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
joiningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/clientutils"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
@@ -286,6 +287,8 @@ func (c *ScopedTokensCommand) List(ctx context.Context, client *authclient.Clien
|
||||
Limit: uint32(pageSize),
|
||||
Cursor: pageKey,
|
||||
WithSecrets: c.withSecrets,
|
||||
// exhaustive user-facing views use MODE_ALL per RFD 0229i
|
||||
ScopeFilter: scopesv1.Filter_builder{Mode: scopesv1.Mode_MODE_ALL}.Build(),
|
||||
}.Build())
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err)
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/lib/asciitable"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/itertools/stream"
|
||||
@@ -84,12 +85,14 @@ func (c *scopesLSCommand) run(cf *CLIConf) error {
|
||||
if err := tc.WithRootClusterClient(ctx, func(clt authclient.ClientI) error {
|
||||
assignments, err = stream.Collect(scopedutils.RangeScopedRoleAssignments(ctx, clt.ScopedAccessServiceClient(), scopedaccessv1.ListScopedRoleAssignmentsRequest_builder{
|
||||
// note that we are using the AllCallerAssignments flag here rather than just looking for
|
||||
// our assignments by username. This flag suppresses standard scope-pinning, which allows
|
||||
// us to see assignments in parent/orthogonal scopes. Generally, scoped commands only show
|
||||
// the subset of state subject to the currently pinned scope, but the purpose of 'tsh scopes ls'
|
||||
// is specifically to discover potential 'tsh login --scope=...' targets, so we want to see
|
||||
// everything regardless of current scope.
|
||||
// our assignments by username. This flag bypasses standard access checks in favor of allowing
|
||||
// all assignments belonging to the callert to be readable. Combined with filter mode ALL, this
|
||||
// allows us to always see all assignments for our identity, regardless of our particular permissions
|
||||
// or pinning state.
|
||||
AllCallerAssignments: true,
|
||||
ScopeFilter: scopesv1.Filter_builder{
|
||||
Mode: scopesv1.Mode_MODE_ALL,
|
||||
}.Build(),
|
||||
}.Build()))
|
||||
return trace.Wrap(err)
|
||||
}); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user