GitHub Proxy part 3.5: caching PluginStaticCredentials (#49472)

* GitHub Proxy part 3.5: caching PluginStaticCredentials

* fix lint
This commit is contained in:
STeve (Xin) Huang
2024-12-03 15:32:00 +00:00
committed by GitHub
parent 3cf8f2ac33
commit b5a6c679fe
15 changed files with 402 additions and 28 deletions
+7
View File
@@ -343,6 +343,10 @@ func EventToGRPC(in types.Event) (*proto.Event, error) {
out.Resource = &proto.Event_AccessListReview{
AccessListReview: accesslistv1conv.ToReviewProto(r),
}
case *types.PluginStaticCredentialsV1:
out.Resource = &proto.Event_PluginStaticCredentials{
PluginStaticCredentials: r,
}
default:
return nil, trace.BadParameter("resource type %T is not supported", in.Resource)
}
@@ -609,6 +613,9 @@ func EventFromGRPC(in *proto.Event) (*types.Event, error) {
} else if r := in.GetIdentityCenterAccountAssignment(); r != nil {
out.Resource = types.Resource153ToLegacy(r)
return &out, nil
} else if r := in.GetPluginStaticCredentials(); r != nil {
out.Resource = r
return &out, nil
} else {
return nil, trace.BadParameter("received unsupported resource %T", in.Resource)
}
+43 -19
View File
@@ -186,6 +186,7 @@ type Event struct {
// *Event_IdentityCenterAccount
// *Event_IdentityCenterPrincipalAssignment
// *Event_IdentityCenterAccountAssignment
// *Event_PluginStaticCredentials
Resource isEvent_Resource `protobuf_oneof:"Resource"`
}
@@ -716,6 +717,13 @@ func (x *Event) GetIdentityCenterAccountAssignment() *v114.AccountAssignment {
return nil
}
func (x *Event) GetPluginStaticCredentials() *types.PluginStaticCredentialsV1 {
if x, ok := x.GetResource().(*Event_PluginStaticCredentials); ok {
return x.PluginStaticCredentials
}
return nil
}
type isEvent_Resource interface {
isEvent_Resource()
}
@@ -1071,6 +1079,11 @@ type Event_IdentityCenterAccountAssignment struct {
IdentityCenterAccountAssignment *v114.AccountAssignment `protobuf:"bytes,74,opt,name=IdentityCenterAccountAssignment,proto3,oneof"`
}
type Event_PluginStaticCredentials struct {
// PluginStaticCredentials is filled in PluginStaticCredentials related events
PluginStaticCredentials *types.PluginStaticCredentialsV1 `protobuf:"bytes,75,opt,name=PluginStaticCredentials,proto3,oneof"`
}
func (*Event_ResourceHeader) isEvent_Resource() {}
func (*Event_CertAuthority) isEvent_Resource() {}
@@ -1209,6 +1222,8 @@ func (*Event_IdentityCenterPrincipalAssignment) isEvent_Resource() {}
func (*Event_IdentityCenterAccountAssignment) isEvent_Resource() {}
func (*Event_PluginStaticCredentials) isEvent_Resource() {}
var File_teleport_legacy_client_proto_event_proto protoreflect.FileDescriptor
var file_teleport_legacy_client_proto_event_proto_rawDesc = []byte{
@@ -1267,7 +1282,7 @@ var file_teleport_legacy_client_proto_event_proto_rawDesc = []byte{
0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x26, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f,
0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2f, 0x76, 0x31, 0x2f,
0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x22, 0xcf, 0x28, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x54, 0x79,
0x22, 0xad, 0x29, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x54, 0x79,
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65,
0x12, 0x3f, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64,
@@ -1586,19 +1601,25 @@ var file_teleport_legacy_client_proto_event_proto_rawDesc = []byte{
0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65,
0x6e, 0x74, 0x48, 0x00, 0x52, 0x1f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x65,
0x6e, 0x74, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67,
0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x31, 0x10, 0x32, 0x4a, 0x04, 0x08,
0x3f, 0x10, 0x40, 0x4a, 0x04, 0x08, 0x44, 0x10, 0x45, 0x52, 0x12, 0x45, 0x78, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x0e, 0x53,
0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x13, 0x41,
0x75, 0x74, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x50, 0x6c,
0x61, 0x6e, 0x2a, 0x2a, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x55, 0x54,
0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x42, 0x34,
0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61,
0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70,
0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x5c, 0x0a, 0x17, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53,
0x74, 0x61, 0x74, 0x69, 0x63, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,
0x18, 0x4b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50,
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x43, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x56, 0x31, 0x48, 0x00, 0x52, 0x17, 0x50, 0x6c, 0x75, 0x67,
0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4a,
0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x31, 0x10, 0x32, 0x4a, 0x04, 0x08, 0x3f, 0x10,
0x40, 0x4a, 0x04, 0x08, 0x44, 0x10, 0x45, 0x52, 0x12, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x0e, 0x53, 0x74, 0x61,
0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x13, 0x41, 0x75, 0x74,
0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x50, 0x6c, 0x61, 0x6e,
0x2a, 0x2a, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a,
0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x55, 0x54, 0x10, 0x01,
0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x42, 0x34, 0x5a, 0x32,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72,
0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1684,6 +1705,7 @@ var file_teleport_legacy_client_proto_event_proto_goTypes = []any{
(*v114.Account)(nil), // 65: teleport.identitycenter.v1.Account
(*v114.PrincipalAssignment)(nil), // 66: teleport.identitycenter.v1.PrincipalAssignment
(*v114.AccountAssignment)(nil), // 67: teleport.identitycenter.v1.AccountAssignment
(*types.PluginStaticCredentialsV1)(nil), // 68: types.PluginStaticCredentialsV1
}
var file_teleport_legacy_client_proto_event_proto_depIdxs = []int32{
0, // 0: proto.Event.Type:type_name -> proto.Operation
@@ -1756,11 +1778,12 @@ var file_teleport_legacy_client_proto_event_proto_depIdxs = []int32{
65, // 67: proto.Event.IdentityCenterAccount:type_name -> teleport.identitycenter.v1.Account
66, // 68: proto.Event.IdentityCenterPrincipalAssignment:type_name -> teleport.identitycenter.v1.PrincipalAssignment
67, // 69: proto.Event.IdentityCenterAccountAssignment:type_name -> teleport.identitycenter.v1.AccountAssignment
70, // [70:70] is the sub-list for method output_type
70, // [70:70] is the sub-list for method input_type
70, // [70:70] is the sub-list for extension type_name
70, // [70:70] is the sub-list for extension extendee
0, // [0:70] is the sub-list for field type_name
68, // 70: proto.Event.PluginStaticCredentials:type_name -> types.PluginStaticCredentialsV1
71, // [71:71] is the sub-list for method output_type
71, // [71:71] is the sub-list for method input_type
71, // [71:71] is the sub-list for extension type_name
71, // [71:71] is the sub-list for extension extendee
0, // [0:71] is the sub-list for field type_name
}
func init() { file_teleport_legacy_client_proto_event_proto_init() }
@@ -1838,6 +1861,7 @@ func file_teleport_legacy_client_proto_event_proto_init() {
(*Event_IdentityCenterAccount)(nil),
(*Event_IdentityCenterPrincipalAssignment)(nil),
(*Event_IdentityCenterAccountAssignment)(nil),
(*Event_PluginStaticCredentials)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -206,5 +206,7 @@ message Event {
// IdentityCenterAccountlAssignment is a resource representing a potential
// Permission Set grant on a specific AWS account.
teleport.identitycenter.v1.AccountAssignment IdentityCenterAccountAssignment = 74;
// PluginStaticCredentials is filled in PluginStaticCredentials related events
types.PluginStaticCredentialsV1 PluginStaticCredentials = 75;
}
}
+2
View File
@@ -108,6 +108,7 @@ type Config struct {
AutoUpdateService services.AutoUpdateServiceGetter
ProvisioningStates services.ProvisioningStates
IdentityCenter services.IdentityCenter
PluginStaticCredentials services.PluginStaticCredentials
}
func (c *Config) CheckAndSetDefaults() error {
@@ -205,6 +206,7 @@ func NewCache(cfg Config) (*cache.Cache, error) {
DynamicWindowsDesktops: cfg.DynamicWindowsDesktops,
ProvisioningStates: cfg.ProvisioningStates,
IdentityCenter: cfg.IdentityCenter,
PluginStaticCredentials: cfg.PluginStaticCredentials,
}
return cache.New(cfg.Setup(cacheCfg))
+1
View File
@@ -370,6 +370,7 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
WebToken: svces.WebTokens(),
WindowsDesktops: svces.WindowsDesktops,
DynamicWindowsDesktops: svces.DynamicWindowsDesktops,
PluginStaticCredentials: svces.PluginStaticCredentials,
})
if err != nil {
return nil, trace.Wrap(err)
+11
View File
@@ -198,6 +198,7 @@ func ForAuth(cfg Config) Config {
{Kind: types.KindIdentityCenterAccount},
{Kind: types.KindIdentityCenterPrincipalAssignment},
{Kind: types.KindIdentityCenterAccountAssignment},
{Kind: types.KindPluginStaticCredentials},
}
cfg.QueueSize = defaults.AuthQueueSize
// We don't want to enable partial health for auth cache because auth uses an event stream
@@ -550,6 +551,7 @@ type Cache struct {
staticHostUsersCache *local.StaticHostUserService
provisioningStatesCache *local.ProvisioningStateService
identityCenterCache *local.IdentityCenterService
pluginStaticCredentialsCache *local.PluginStaticCredentialsService
// closed indicates that the cache has been closed
closed atomic.Bool
@@ -784,6 +786,8 @@ type Config struct {
// IdentityCenter is the upstream Identity Center service that we're caching
IdentityCenter services.IdentityCenter
// PluginStaticCredentials is the plugin static credentials services
PluginStaticCredentials services.PluginStaticCredentials
}
// CheckAndSetDefaults checks parameters and sets default values
@@ -1023,6 +1027,12 @@ func New(config Config) (*Cache, error) {
return nil, trace.Wrap(err)
}
pluginStaticCredentialsCache, err := local.NewPluginStaticCredentialsService(config.Backend)
if err != nil {
cancel()
return nil, trace.Wrap(err)
}
cs := &Cache{
ctx: ctx,
cancel: cancel,
@@ -1070,6 +1080,7 @@ func New(config Config) (*Cache, error) {
staticHostUsersCache: staticHostUserCache,
provisioningStatesCache: provisioningStatesCache,
identityCenterCache: identityCenterCache,
pluginStaticCredentialsCache: pluginStaticCredentialsCache,
Logger: log.WithFields(log.Fields{
teleport.ComponentKey: config.Component,
}),
+32 -9
View File
@@ -141,17 +141,19 @@ type testPack struct {
autoUpdateService services.AutoUpdateService
provisioningStates services.ProvisioningStates
identityCenter services.IdentityCenter
pluginStaticCredentials *local.PluginStaticCredentialsService
}
// testFuncs are functions to support testing an object in a cache.
type testFuncs[T types.Resource] struct {
newResource func(string) (T, error)
create func(context.Context, T) error
list func(context.Context) ([]T, error)
cacheGet func(context.Context, string) (T, error)
cacheList func(context.Context) ([]T, error)
update func(context.Context, T) error
deleteAll func(context.Context) error
newResource func(string) (T, error)
create func(context.Context, T) error
list func(context.Context) ([]T, error)
cacheGet func(context.Context, string) (T, error)
cacheList func(context.Context) ([]T, error)
update func(context.Context, T) error
deleteAll func(context.Context) error
changeResource func(T)
}
// testFuncs153 are functions to support testing an RFD153-style resource in a cache.
@@ -402,6 +404,11 @@ func newPackWithoutCache(dir string, opts ...packOption) (*testPack, error) {
return nil, trace.Wrap(err)
}
p.pluginStaticCredentials, err = local.NewPluginStaticCredentialsService(p.backend)
if err != nil {
return nil, trace.Wrap(err)
}
return p, nil
}
@@ -455,6 +462,7 @@ func newPack(dir string, setupConfig func(c Config) Config, opts ...packOption)
AutoUpdateService: p.autoUpdateService,
ProvisioningStates: p.provisioningStates,
IdentityCenter: p.identityCenter,
PluginStaticCredentials: p.pluginStaticCredentials,
MaxRetryPeriod: 200 * time.Millisecond,
EventsC: p.eventsC,
}))
@@ -868,6 +876,7 @@ func TestCompletenessInit(t *testing.T) {
ProvisioningStates: p.provisioningStates,
MaxRetryPeriod: 200 * time.Millisecond,
IdentityCenter: p.identityCenter,
PluginStaticCredentials: p.pluginStaticCredentials,
EventsC: p.eventsC,
}))
require.NoError(t, err)
@@ -951,6 +960,7 @@ func TestCompletenessReset(t *testing.T) {
AutoUpdateService: p.autoUpdateService,
ProvisioningStates: p.provisioningStates,
IdentityCenter: p.identityCenter,
PluginStaticCredentials: p.pluginStaticCredentials,
MaxRetryPeriod: 200 * time.Millisecond,
EventsC: p.eventsC,
}))
@@ -1161,6 +1171,7 @@ func TestListResources_NodesTTLVariant(t *testing.T) {
AutoUpdateService: p.autoUpdateService,
ProvisioningStates: p.provisioningStates,
IdentityCenter: p.identityCenter,
PluginStaticCredentials: p.pluginStaticCredentials,
MaxRetryPeriod: 200 * time.Millisecond,
EventsC: p.eventsC,
neverOK: true, // ensure reads are never healthy
@@ -1256,6 +1267,7 @@ func initStrategy(t *testing.T) {
AutoUpdateService: p.autoUpdateService,
ProvisioningStates: p.provisioningStates,
IdentityCenter: p.identityCenter,
PluginStaticCredentials: p.pluginStaticCredentials,
MaxRetryPeriod: 200 * time.Millisecond,
EventsC: p.eventsC,
}))
@@ -2926,10 +2938,20 @@ func TestStaticHostUsers(t *testing.T) {
func testResources[T types.Resource](t *testing.T, p *testPack, funcs testFuncs[T]) {
ctx := context.Background()
if funcs.changeResource == nil {
funcs.changeResource = func(t T) {
if t.Expiry().IsZero() {
t.SetExpiry(time.Now().Add(30 * time.Minute))
} else {
t.SetExpiry(t.Expiry().Add(30 * time.Minute))
}
}
}
// Create a resource.
r, err := funcs.newResource("test-sp")
require.NoError(t, err)
r.SetExpiry(time.Now().Add(30 * time.Minute))
funcs.changeResource(r)
err = funcs.create(ctx, r)
require.NoError(t, err)
@@ -2963,7 +2985,7 @@ func testResources[T types.Resource](t *testing.T, p *testPack, funcs testFuncs[
// update is optional as not every resource implements it
if funcs.update != nil {
// Update the resource and upsert it into the backend again.
r.SetExpiry(r.Expiry().Add(30 * time.Minute))
funcs.changeResource(r)
err = funcs.update(ctx, r)
require.NoError(t, err)
}
@@ -3521,6 +3543,7 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) {
types.KindIdentityCenterAccount: types.Resource153ToLegacy(newIdentityCenterAccount("some_account")),
types.KindIdentityCenterAccountAssignment: types.Resource153ToLegacy(newIdentityCenterAccountAssignment("some_account_assignment")),
types.KindIdentityCenterPrincipalAssignment: types.Resource153ToLegacy(newIdentityCenterPrincipalAssignment("some_principal_assignment")),
types.KindPluginStaticCredentials: &types.PluginStaticCredentialsV1{},
}
for name, cfg := range cases {
+15
View File
@@ -176,6 +176,7 @@ type cacheCollections struct {
identityCenterAccounts collectionReader[identityCenterAccountGetter]
identityCenterPrincipalAssignments collectionReader[identityCenterPrincipalAssignmentGetter]
identityCenterAccountAssignments collectionReader[identityCenterAccountAssignmentGetter]
pluginStaticCredentials collectionReader[pluginStaticCredentialsGetter]
}
// setupCollections returns a registry of collections.
@@ -784,6 +785,20 @@ func setupCollections(c *Cache, watches []types.WatchKind) (*cacheCollections, e
}
collections.byKind[resourceKind] = collections.identityCenterAccountAssignments
case types.KindPluginStaticCredentials:
if c.PluginStaticCredentials == nil {
return nil, trace.BadParameter("missing parameter PluginStaticCredentials")
}
collections.pluginStaticCredentials = &genericCollection[
types.PluginStaticCredentials,
pluginStaticCredentialsGetter,
pluginStaticCredentialsExecutor,
]{
cache: c,
watch: watch,
}
collections.byKind[resourceKind] = collections.pluginStaticCredentials
default:
return nil, trace.BadParameter("resource %q is not supported", watch.Kind)
}
+84
View File
@@ -0,0 +1,84 @@
// Teleport
// Copyright (C) 2024 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 (
"context"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types"
)
func (c *Cache) GetPluginStaticCredentials(ctx context.Context, name string) (types.PluginStaticCredentials, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetPluginStaticCredentials")
defer span.End()
rg, err := readCollectionCache(c, c.collections.pluginStaticCredentials)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
return rg.reader.GetPluginStaticCredentials(ctx, name)
}
func (c *Cache) GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetPluginStaticCredentialsByLabels")
defer span.End()
rg, err := readCollectionCache(c, c.collections.pluginStaticCredentials)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
return rg.reader.GetPluginStaticCredentialsByLabels(ctx, labels)
}
type pluginStaticCredentialsGetter interface {
GetPluginStaticCredentials(ctx context.Context, name string) (types.PluginStaticCredentials, error)
GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error)
}
var _ executor[types.PluginStaticCredentials, pluginStaticCredentialsGetter] = pluginStaticCredentialsExecutor{}
type pluginStaticCredentialsExecutor struct{}
func (pluginStaticCredentialsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.PluginStaticCredentials, error) {
return cache.pluginStaticCredentialsCache.GetAllPluginStaticCredentials(ctx)
}
func (pluginStaticCredentialsExecutor) upsert(ctx context.Context, cache *Cache, resource types.PluginStaticCredentials) error {
_, err := cache.pluginStaticCredentialsCache.UpsertPluginStaticCredentials(ctx, resource)
return trace.Wrap(err)
}
func (pluginStaticCredentialsExecutor) deleteAll(ctx context.Context, cache *Cache) error {
return cache.pluginStaticCredentialsCache.DeleteAllPluginStaticCredentials(ctx)
}
func (pluginStaticCredentialsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error {
return cache.pluginStaticCredentialsCache.DeletePluginStaticCredentials(ctx, resource.GetName())
}
func (pluginStaticCredentialsExecutor) isSingleton() bool { return false }
func (pluginStaticCredentialsExecutor) getReader(cache *Cache, cacheOK bool) pluginStaticCredentialsGetter {
if cacheOK {
return cache.pluginStaticCredentialsCache
}
return cache.Config.PluginStaticCredentials
}
+104
View File
@@ -0,0 +1,104 @@
// Teleport
// Copyright (C) 2024 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 (
"context"
"testing"
"time"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
)
func TestPluginStaticCredentials(t *testing.T) {
t.Parallel()
p, err := newPack(t.TempDir(), ForAuth)
require.NoError(t, err)
t.Cleanup(p.Close)
makeLabels := func(name string) map[string]string {
return map[string]string{
"resource-label": "label-for-" + name,
}
}
cacheGets := []struct {
name string
fn func(context.Context, string) (types.PluginStaticCredentials, error)
}{
{
name: "GetPluginStaticCredentials",
fn: p.cache.GetPluginStaticCredentials,
},
{
name: "GetPluginStaticCredentialsByLabels",
fn: func(ctx context.Context, name string) (types.PluginStaticCredentials, error) {
creds, err := p.cache.GetPluginStaticCredentialsByLabels(ctx, makeLabels(name))
if err != nil {
return nil, trace.Wrap(err)
}
if len(creds) != 1 {
return nil, trace.CompareFailed("expecting one creds for this test but got %v", len(creds))
}
return creds[0], nil
},
},
}
for _, cacheGet := range cacheGets {
t.Run(cacheGet.name, func(t *testing.T) {
// Empty backend before the test.
err := p.pluginStaticCredentials.DeleteAllPluginStaticCredentials(context.Background())
require.NoError(t, err)
testResources(t, p, testFuncs[types.PluginStaticCredentials]{
newResource: func(name string) (types.PluginStaticCredentials, error) {
return types.NewPluginStaticCredentials(
types.Metadata{
Name: name,
Labels: makeLabels(name),
},
types.PluginStaticCredentialsSpecV1{
Credentials: &types.PluginStaticCredentialsSpecV1_APIToken{
APIToken: "some-token",
},
})
},
create: p.pluginStaticCredentials.CreatePluginStaticCredentials,
list: p.pluginStaticCredentials.GetAllPluginStaticCredentials,
update: func(ctx context.Context, cred types.PluginStaticCredentials) error {
_, err := p.pluginStaticCredentials.UpdatePluginStaticCredentials(ctx, cred)
return err
},
deleteAll: p.pluginStaticCredentials.DeleteAllPluginStaticCredentials,
cacheList: p.cache.pluginStaticCredentialsCache.GetAllPluginStaticCredentials,
cacheGet: cacheGet.fn,
changeResource: func(cred types.PluginStaticCredentials) {
// types.PluginStaticCredentials does not support Expires. Let's
// use labels.
labels := cred.GetStaticLabels()
labels["now"] = time.Now().String()
cred.SetStaticLabels(labels)
},
})
})
}
}
+1
View File
@@ -2552,6 +2552,7 @@ func (process *TeleportProcess) newAccessCacheForServices(cfg accesspoint.Config
cfg.AutoUpdateService = services.AutoUpdateService
cfg.ProvisioningStates = services.ProvisioningStates
cfg.IdentityCenter = services.IdentityCenter
cfg.PluginStaticCredentials = services.PluginStaticCredentials
return accesspoint.NewCache(cfg)
}
+2
View File
@@ -248,6 +248,8 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type
parser = newIdentityCenterPrincipalAssignmentParser()
case types.KindIdentityCenterAccountAssignment:
parser = newIdentityCenterAccountAssignmentParser()
case types.KindPluginStaticCredentials:
parser = newPluginStaticCredentialsParser()
default:
if watch.AllowPartialSuccess {
continue
@@ -23,6 +23,7 @@ import (
"github.com/gravitational/trace"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/services"
@@ -94,4 +95,62 @@ func (p *PluginStaticCredentialsService) DeletePluginStaticCredentials(ctx conte
return trace.Wrap(p.svc.DeleteResource(ctx, name))
}
// GetAllPluginStaticCredentials will get all plugin static credentials. Cache
// use only.
func (p *PluginStaticCredentialsService) GetAllPluginStaticCredentials(ctx context.Context) ([]types.PluginStaticCredentials, error) {
creds, err := p.svc.GetResources(ctx)
return creds, trace.Wrap(err)
}
// DeleteAllPluginStaticCredentials will remove all plugin static credentials.
// Cache use only.
func (p *PluginStaticCredentialsService) DeleteAllPluginStaticCredentials(ctx context.Context) error {
return trace.Wrap(p.svc.DeleteAllResources(ctx))
}
// UpsertPluginStaticCredentials will upsert a plugin static credentials. Cache
// use only.
func (p *PluginStaticCredentialsService) UpsertPluginStaticCredentials(ctx context.Context, item types.PluginStaticCredentials) (types.PluginStaticCredentials, error) {
cred, err := p.svc.UpsertResource(ctx, item)
return cred, trace.Wrap(err)
}
var _ services.PluginStaticCredentials = (*PluginStaticCredentialsService)(nil)
type pluginStaticCredentialsParser struct {
baseParser
}
func newPluginStaticCredentialsParser() *pluginStaticCredentialsParser {
return &pluginStaticCredentialsParser{
baseParser: newBaseParser(backend.NewKey(pluginStaticCredentialsPrefix)),
}
}
func (p *pluginStaticCredentialsParser) parse(event backend.Event) (types.Resource, error) {
switch event.Type {
case types.OpDelete:
parts := event.Item.Key.Components()
if len(parts) != 2 {
return nil, trace.BadParameter("malformed key for %s event: %s", types.KindPluginStaticCredentials, event.Item.Key)
}
return &types.ResourceHeader{
Kind: types.KindPluginStaticCredentials,
Version: types.V1,
Metadata: types.Metadata{
Name: parts[1],
Namespace: apidefaults.Namespace,
Description: parts[0],
},
}, nil
case types.OpPut:
return services.UnmarshalPluginStaticCredentials(
event.Item.Value,
services.WithExpires(event.Item.Expires),
services.WithRevision(event.Item.Revision))
default:
return nil, trace.BadParameter("event %v is not supported", event.Type)
}
}
@@ -88,6 +88,11 @@ func TestPluginStaticCredentialsCRUD(t *testing.T) {
cmpopts.IgnoreFields(types.Metadata{}, "Revision"),
))
// Fetch all.
all, err := service.GetAllPluginStaticCredentials(ctx)
require.NoError(t, err)
require.Len(t, all, 2)
// Try to fetch a static credential that doesn't exist.
_, err = service.GetPluginStaticCredentials(ctx, "doesnotexist")
require.True(t, trace.IsNotFound(err))
@@ -132,4 +137,17 @@ func TestPluginStaticCredentialsCRUD(t *testing.T) {
// Try to delete a static credential that doesn't exist.
err = service.DeletePluginStaticCredentials(ctx, "doesnotexist")
require.True(t, trace.IsNotFound(err))
// Upsert.
_, err = service.UpsertPluginStaticCredentials(ctx, cred1)
require.NoError(t, err)
_, err = service.GetPluginStaticCredentials(ctx, cred1.GetName())
require.NoError(t, err)
// Delete all.
err = service.DeleteAllPluginStaticCredentials(ctx)
require.NoError(t, err)
all, err = service.GetAllPluginStaticCredentials(ctx)
require.NoError(t, err)
require.Empty(t, all)
}
@@ -20,6 +20,7 @@ package services
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
@@ -45,4 +46,24 @@ func TestMarshalPluginStaticCredentialsRoundTrip(t *testing.T) {
unmarshaled, err := UnmarshalPluginStaticCredentials(payload)
require.NoError(t, err)
require.Empty(t, cmp.Diff(creds, unmarshaled))
// TODO(greedy52) PluginStaticCredentials uses protojson for marshaling
// and unmarshaling. Unfortunately Expires (*time.Time) is not supported
// and becomes zero after the marshaling. Unlikely Expires will ever be
// needed for PluginStaticCredentials in production but this can cause
// unexpected issues in other places (like resource.SetExpiry in cache
// tests).
t.Run("with expires", func(t *testing.T) {
t.Skip("PluginStaticCredentials does not marshal Expires")
now := time.Now()
creds.SetExpiry(now)
payload, err := MarshalPluginStaticCredentials(creds)
require.NoError(t, err)
unmarshaled, err := UnmarshalPluginStaticCredentials(payload)
require.NoError(t, err)
require.Empty(t, cmp.Diff(creds, unmarshaled))
require.Equal(t, now, unmarshaled.Expiry())
})
}