Skip to content

Commit c0f79b0

Browse files
committed
high level scoped role/assignment cache, api, and commands (#56331)
1 parent 613dfaa commit c0f79b0

32 files changed

+2502
-453
lines changed

api/client/client.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import (
9393
recordingmetadatav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/recordingmetadata/v1"
9494
resourceusagepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/resourceusage/v1"
9595
samlidppb "github.com/gravitational/teleport/api/gen/proto/go/teleport/samlidp/v1"
96+
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
9697
secreportsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/secreports/v1"
9798
stableunixusersv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/stableunixusers/v1"
9899
summarizerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/summarizer/v1"
@@ -839,6 +840,12 @@ func (c *Client) UpsertDeviceResource(ctx context.Context, res *types.DeviceV1)
839840
return types.DeviceToResource(upserted), nil
840841
}
841842

843+
// ScopedAccessServiceClient returns an unadorned Scoped Access Service client, using the underlying
844+
// Auth gRPC connection.
845+
func (c *Client) ScopedAccessServiceClient() scopedaccessv1.ScopedAccessServiceClient {
846+
return scopedaccessv1.NewScopedAccessServiceClient(c.conn)
847+
}
848+
842849
// LoginRuleClient returns an unadorned Login Rule client, using the underlying
843850
// Auth gRPC connection.
844851
// Clients connecting to non-Enterprise clusters, or older Teleport versions,

api/client/events.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
3131
provisioningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/provisioning/v1"
3232
recordingencryptionv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/recordingencryption/v1"
33-
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
33+
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
3434
userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2"
3535
usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1"
3636
workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1"
@@ -129,11 +129,11 @@ func EventToGRPC(in types.Event) (*proto.Event, error) {
129129
out.Resource = &proto.Event_AutoUpdateAgentReport{
130130
AutoUpdateAgentReport: r.UnwrapT(),
131131
}
132-
case types.Resource153UnwrapperT[*accessv1.ScopedRole]:
132+
case types.Resource153UnwrapperT[*scopedaccessv1.ScopedRole]:
133133
out.Resource = &proto.Event_ScopedRole{
134134
ScopedRole: r.UnwrapT(),
135135
}
136-
case types.Resource153UnwrapperT[*accessv1.ScopedRoleAssignment]:
136+
case types.Resource153UnwrapperT[*scopedaccessv1.ScopedRoleAssignment]:
137137
out.Resource = &proto.Event_ScopedRoleAssignment{
138138
ScopedRoleAssignment: r.UnwrapT(),
139139
}

lib/auth/auth.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ import (
124124
"github.com/gravitational/teleport/lib/observability/tracing"
125125
"github.com/gravitational/teleport/lib/release"
126126
"github.com/gravitational/teleport/lib/resourceusage"
127+
scopedaccesscache "github.com/gravitational/teleport/lib/scopes/cache/access"
127128
"github.com/gravitational/teleport/lib/service/servicecfg"
128129
"github.com/gravitational/teleport/lib/services"
129130
"github.com/gravitational/teleport/lib/services/local"
@@ -533,6 +534,9 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (as *Server, err error) {
533534
return nil, trace.Wrap(err, "creating VnetConfigService")
534535
}
535536
}
537+
if cfg.ScopedAccess == nil {
538+
cfg.ScopedAccess = local.NewScopedAccessService(cfg.Backend)
539+
}
536540

537541
if cfg.Logger == nil {
538542
cfg.Logger = slog.With(teleport.ComponentKey, teleport.ComponentAuth)
@@ -561,6 +565,14 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (as *Server, err error) {
561565
}
562566
}
563567

568+
scopedAccessCache, err := scopedaccesscache.NewCache(scopedaccesscache.CacheConfig{
569+
Events: cfg.Events,
570+
Reader: cfg.ScopedAccess,
571+
})
572+
if err != nil {
573+
return nil, trace.Wrap(err)
574+
}
575+
564576
services := &Services{
565577
TrustInternal: cfg.Trust,
566578
PresenceInternal: cfg.Presence,
@@ -635,6 +647,8 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (as *Server, err error) {
635647
Unstable: local.NewUnstableService(cfg.Backend, cfg.AssertionReplayService),
636648
Services: services,
637649
Cache: services,
650+
scopedAccessBackend: cfg.ScopedAccess,
651+
scopedAccessCache: scopedAccessCache,
638652
keyStore: cfg.KeyStore,
639653
traceClient: cfg.TraceClient,
640654
fips: cfg.FIPS,
@@ -1100,6 +1114,13 @@ type Server struct {
11001114
// method on Services instead.
11011115
authclient.Cache
11021116

1117+
// scopedAccessCache is a specialized cache that provides read methods for select
1118+
// scoped access control resources.
1119+
scopedAccessCache *scopedaccesscache.Cache
1120+
1121+
// scopedAccessBackend is the backend service for scoped access control resources.
1122+
scopedAccessBackend services.ScopedAccess
1123+
11031124
// ReadOnlyCache is a specialized cache that provides read-only shared references
11041125
// in certain performance-critical paths where deserialization/cloning may be too
11051126
// expensive at scale.

lib/auth/auth_with_roles_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import (
5353
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
5454
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
5555
mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1"
56-
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
56+
scopedaccessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
5757
trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
5858
userpreferencesv1 "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1"
5959
"github.com/gravitational/teleport/api/metadata"
@@ -82,7 +82,7 @@ import (
8282
"github.com/gravitational/teleport/lib/modules"
8383
"github.com/gravitational/teleport/lib/modules/modulestest"
8484
"github.com/gravitational/teleport/lib/okta/oktatest"
85-
scopedrole "github.com/gravitational/teleport/lib/scopes/roles"
85+
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
8686
"github.com/gravitational/teleport/lib/services"
8787
"github.com/gravitational/teleport/lib/services/local"
8888
"github.com/gravitational/teleport/lib/session"
@@ -9937,10 +9937,10 @@ func TestScopedRoleEvents(t *testing.T) {
99379937
watcher, err := client.NewWatcher(ctx, types.Watch{
99389938
Kinds: []types.WatchKind{
99399939
{
9940-
Kind: scopedrole.KindScopedRole,
9940+
Kind: scopedaccess.KindScopedRole,
99419941
},
99429942
{
9943-
Kind: scopedrole.KindScopedRoleAssignment,
9943+
Kind: scopedaccess.KindScopedRoleAssignment,
99449944
},
99459945
},
99469946
})
@@ -9969,31 +9969,31 @@ func TestScopedRoleEvents(t *testing.T) {
99699969
require.Equal(t, types.OpInit, event.Type)
99709970

99719971
// Create a ScopedRole and verify create event is well-formed.
9972-
role := &accessv1.ScopedRole{
9973-
Kind: scopedrole.KindScopedRole,
9972+
role := &scopedaccessv1.ScopedRole{
9973+
Kind: scopedaccess.KindScopedRole,
99749974
Metadata: &headerv1.Metadata{
99759975
Name: "test-role",
99769976
},
99779977
Scope: "/",
9978-
Spec: &accessv1.ScopedRoleSpec{
9978+
Spec: &scopedaccessv1.ScopedRoleSpec{
99799979
AssignableScopes: []string{"/foo", "/bar"},
99809980
},
99819981
Version: types.V1,
99829982
}
99839983

9984-
crsp, err := service.CreateScopedRole(ctx, &accessv1.CreateScopedRoleRequest{
9984+
crsp, err := service.CreateScopedRole(ctx, &scopedaccessv1.CreateScopedRoleRequest{
99859985
Role: role,
99869986
})
99879987
require.NoError(t, err)
99889988

99899989
event = getNextEvent()
99909990
require.Equal(t, types.OpPut, event.Type)
99919991

9992-
resource := (event.Resource).(types.Resource153UnwrapperT[*accessv1.ScopedRole]).UnwrapT()
9992+
resource := (event.Resource).(types.Resource153UnwrapperT[*scopedaccessv1.ScopedRole]).UnwrapT()
99939993
require.Empty(t, cmp.Diff(crsp.Role, resource, protocmp.Transform() /* deliberately not ignoring revision */))
99949994

99959995
// delete the role and verify delete event is well-formed.
9996-
_, err = service.DeleteScopedRole(ctx, &accessv1.DeleteScopedRoleRequest{
9996+
_, err = service.DeleteScopedRole(ctx, &scopedaccessv1.DeleteScopedRoleRequest{
99979997
Name: role.Metadata.Name,
99989998
})
99999999
require.NoError(t, err)
@@ -10002,29 +10002,29 @@ func TestScopedRoleEvents(t *testing.T) {
1000210002
require.Equal(t, types.OpDelete, event.Type)
1000310003

1000410004
require.Empty(t, cmp.Diff(&types.ResourceHeader{
10005-
Kind: scopedrole.KindScopedRole,
10005+
Kind: scopedaccess.KindScopedRole,
1000610006
Metadata: types.Metadata{
1000710007
Name: role.Metadata.Name,
1000810008
},
1000910009
}, event.Resource.(*types.ResourceHeader), protocmp.Transform()))
1001010010

1001110011
// recreate scoped role so that we can use it for testing assignment events
10012-
crsp, err = service.CreateScopedRole(ctx, &accessv1.CreateScopedRoleRequest{
10012+
crsp, err = service.CreateScopedRole(ctx, &scopedaccessv1.CreateScopedRoleRequest{
1001310013
Role: role,
1001410014
})
1001510015
require.NoError(t, err)
1001610016

1001710017
_ = getNextEvent() // drain the role create event
1001810018

10019-
assignment := &accessv1.ScopedRoleAssignment{
10020-
Kind: scopedrole.KindScopedRoleAssignment,
10019+
assignment := &scopedaccessv1.ScopedRoleAssignment{
10020+
Kind: scopedaccess.KindScopedRoleAssignment,
1002110021
Metadata: &headerv1.Metadata{
1002210022
Name: uuid.New().String(),
1002310023
},
1002410024
Scope: "/",
10025-
Spec: &accessv1.ScopedRoleAssignmentSpec{
10025+
Spec: &scopedaccessv1.ScopedRoleAssignmentSpec{
1002610026
User: "alice",
10027-
Assignments: []*accessv1.Assignment{
10027+
Assignments: []*scopedaccessv1.Assignment{
1002810028
{
1002910029
Role: role.Metadata.Name,
1003010030
Scope: "/foo",
@@ -10034,7 +10034,7 @@ func TestScopedRoleEvents(t *testing.T) {
1003410034
Version: types.V1,
1003510035
}
1003610036

10037-
acrsp, err := service.CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{
10037+
acrsp, err := service.CreateScopedRoleAssignment(ctx, &scopedaccessv1.CreateScopedRoleAssignmentRequest{
1003810038
Assignment: assignment,
1003910039
RoleRevisions: map[string]string{
1004010040
role.Metadata.Name: crsp.Role.Metadata.Revision,
@@ -10044,11 +10044,11 @@ func TestScopedRoleEvents(t *testing.T) {
1004410044

1004510045
event = getNextEvent()
1004610046
require.Equal(t, types.OpPut, event.Type)
10047-
assignmentResource := (event.Resource).(types.Resource153UnwrapperT[*accessv1.ScopedRoleAssignment]).UnwrapT()
10047+
assignmentResource := (event.Resource).(types.Resource153UnwrapperT[*scopedaccessv1.ScopedRoleAssignment]).UnwrapT()
1004810048
require.Empty(t, cmp.Diff(acrsp.Assignment, assignmentResource, protocmp.Transform() /* deliberately not ignoring revision */))
1004910049

1005010050
// delete the assignment and verify delete event is well-formed.
10051-
_, err = service.DeleteScopedRoleAssignment(ctx, &accessv1.DeleteScopedRoleAssignmentRequest{
10051+
_, err = service.DeleteScopedRoleAssignment(ctx, &scopedaccessv1.DeleteScopedRoleAssignmentRequest{
1005210052
Name: assignment.Metadata.Name,
1005310053
})
1005410054
require.NoError(t, err)
@@ -10057,7 +10057,7 @@ func TestScopedRoleEvents(t *testing.T) {
1005710057
require.Equal(t, types.OpDelete, event.Type)
1005810058

1005910059
require.Empty(t, cmp.Diff(&types.ResourceHeader{
10060-
Kind: scopedrole.KindScopedRoleAssignment,
10060+
Kind: scopedaccess.KindScopedRoleAssignment,
1006110061
Metadata: types.Metadata{
1006210062
Name: assignment.Metadata.Name,
1006310063
},

lib/auth/grpcserver.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5659,6 +5659,9 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
56595659

56605660
scopedAccessControl, err := scopedaccess.New(scopedaccess.Config{
56615661
Authorizer: cfg.Authorizer,
5662+
Reader: cfg.AuthServer.scopedAccessCache,
5663+
Writer: cfg.AuthServer.scopedAccessBackend,
5664+
Logger: logger,
56625665
})
56635666
if err != nil {
56645667
return nil, trace.Wrap(err, "creating scoped access control service")

lib/auth/init.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,17 @@ type InitConfig struct {
402402
// It allows for late initialization of the summarizer in the enterprise
403403
// plugin. The summarizer itself summarizes session recordings.
404404
SessionSummarizerProvider *summarizer.SessionSummarizerProvider
405+
406+
// RunWhileLockedRetryInterval defines the interval at which the auth server retries
407+
// a locking operation for backend objects.
408+
// This setting is particularly useful in test environments,
409+
// as it can help accelerate operations such as updating the access list,
410+
// especially when the list is also being modified concurrently by the background
411+
// eligibility handler.
412+
RunWhileLockedRetryInterval time.Duration
413+
414+
// ScopedAccess is a service that manages scoped access resources.
415+
ScopedAccess services.ScopedAccess
405416
}
406417

407418
// Init instantiates and configures an instance of AuthServer

0 commit comments

Comments
 (0)