From 7c0bfa304f936ab3f573b2991bd00a9f28cb4dd9 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Wed, 5 Feb 2025 20:11:14 -0800 Subject: [PATCH 1/3] Add bootstrap support for AutoUpdateConfig/AutoUpdateVersion --- lib/auth/init_test.go | 39 ++++++++++++++++++++++++ lib/services/local/autoupdate.go | 52 ++++++++++++++++++++++++++++++++ lib/services/local/resource.go | 10 ++++++ lib/services/resource.go | 15 +++++++++ 4 files changed, 116 insertions(+) diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index f4218479f302b..11e279895dc21 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -2283,3 +2283,42 @@ func Test_createPresetDatabaseObjectImportRule(t *testing.T) { }) } } + +// TestInitWithAutoUpdateBootstrap verifies that auth +func TestInitWithAutoUpdateBootstrap(t *testing.T) { + t.Parallel() + + const autoUpdateConfigYAML = `kind: autoupdate_config +metadata: + name: autoupdate-config +spec: + tools: + mode: enabled +version: v1` + const autoUpdateVersionYAML = `kind: autoupdate_version +metadata: + name: autoupdate-version +spec: + tools: + target_version: 1.2.3 +version: v1` + + ctx := context.Background() + + cfg := setupConfig(t) + cfg.BootstrapResources = []types.Resource{ + resourceFromYAML(t, autoUpdateConfigYAML), + resourceFromYAML(t, autoUpdateVersionYAML), + } + + auth, err := Init(ctx, cfg) + require.NoError(t, err) + + config, err := auth.GetAutoUpdateConfig(ctx) + assert.NoError(t, err) + assert.Equal(t, "enabled", config.GetSpec().GetTools().GetMode()) + + version, err := auth.GetAutoUpdateVersion(ctx) + assert.NoError(t, err) + assert.Equal(t, "1.2.3", version.GetSpec().GetTools().GetTargetVersion()) +} diff --git a/lib/services/local/autoupdate.go b/lib/services/local/autoupdate.go index 93a5142ca81a1..2abda7b94c40c 100644 --- a/lib/services/local/autoupdate.go +++ b/lib/services/local/autoupdate.go @@ -212,3 +212,55 @@ func (s *AutoUpdateService) GetAutoUpdateAgentRollout(ctx context.Context) (*aut func (s *AutoUpdateService) DeleteAutoUpdateAgentRollout(ctx context.Context) error { return trace.Wrap(s.rollout.DeleteResource(ctx, types.MetaNameAutoUpdateAgentRollout)) } + +// itemFromAutoUpdateConfig generates `backend.Item` from `AutoUpdateConfig` resource type. +func itemFromAutoUpdateConfig(config *autoupdate.AutoUpdateConfig) (*backend.Item, error) { + if err := update.ValidateAutoUpdateConfig(config); err != nil { + return nil, trace.Wrap(err) + } + rev, err := types.GetRevision(config) + if err != nil { + return nil, trace.Wrap(err) + } + value, err := services.MarshalProtoResource[*autoupdate.AutoUpdateConfig](config) + if err != nil { + return nil, trace.Wrap(err) + } + expires, err := types.GetExpiry(config) + if err != nil { + return nil, trace.Wrap(err) + } + item := &backend.Item{ + Key: backend.NewKey(autoUpdateConfigPrefix).AppendKey(backend.NewKey(types.MetaNameAutoUpdateConfig)), + Value: value, + Expires: expires, + Revision: rev, + } + return item, nil +} + +// itemFromAutoUpdateVersion generates `backend.Item` from `AutoUpdateVersion` resource type. +func itemFromAutoUpdateVersion(version *autoupdate.AutoUpdateVersion) (*backend.Item, error) { + if err := update.ValidateAutoUpdateVersion(version); err != nil { + return nil, trace.Wrap(err) + } + rev, err := types.GetRevision(version) + if err != nil { + return nil, trace.Wrap(err) + } + value, err := services.MarshalProtoResource[*autoupdate.AutoUpdateVersion](version) + if err != nil { + return nil, trace.Wrap(err) + } + expires, err := types.GetExpiry(version) + if err != nil { + return nil, trace.Wrap(err) + } + item := &backend.Item{ + Key: backend.NewKey(autoUpdateVersionPrefix).AppendKey(backend.NewKey(types.MetaNameAutoUpdateVersion)), + Value: value, + Expires: expires, + Revision: rev, + } + return item, nil +} diff --git a/lib/services/local/resource.go b/lib/services/local/resource.go index eb4b6ed4f44ca..4e2f60ab2d834 100644 --- a/lib/services/local/resource.go +++ b/lib/services/local/resource.go @@ -24,6 +24,7 @@ import ( "github.com/gravitational/trace" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/services" @@ -97,6 +98,15 @@ func itemsFromResource(resource types.Resource) ([]backend.Item, error) { item, err = itemFromClusterNetworkingConfig(r) case types.AuthPreference: item, err = itemFromAuthPreference(r) + case types.Resource153Unwrapper: + switch r153 := r.Unwrap().(type) { + case *autoupdatev1pb.AutoUpdateConfig: + item, err = itemFromAutoUpdateConfig(r153) + case *autoupdatev1pb.AutoUpdateVersion: + item, err = itemFromAutoUpdateVersion(r153) + default: + return nil, trace.NotImplemented("cannot itemFrom resource of type %T", resource) + } default: return nil, trace.NotImplemented("cannot itemFrom resource of type %T", resource) } diff --git a/lib/services/resource.go b/lib/services/resource.go index dd0382c439fef..05d2af7abd8b6 100644 --- a/lib/services/resource.go +++ b/lib/services/resource.go @@ -31,6 +31,7 @@ import ( "google.golang.org/protobuf/protoadapt" "google.golang.org/protobuf/types/known/timestamppb" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" "github.com/gravitational/teleport/api/types" @@ -715,6 +716,20 @@ func init() { } return types.Resource153ToLegacy(b), nil }) + RegisterResourceUnmarshaler(types.KindAutoUpdateConfig, func(bytes []byte, option ...MarshalOption) (types.Resource, error) { + c := &autoupdatev1pb.AutoUpdateConfig{} + if err := protojson.Unmarshal(bytes, c); err != nil { + return nil, trace.Wrap(err) + } + return types.Resource153ToLegacy(c), nil + }) + RegisterResourceUnmarshaler(types.KindAutoUpdateVersion, func(bytes []byte, option ...MarshalOption) (types.Resource, error) { + v := &autoupdatev1pb.AutoUpdateVersion{} + if err := protojson.Unmarshal(bytes, v); err != nil { + return nil, trace.Wrap(err) + } + return types.Resource153ToLegacy(v), nil + }) } // CheckAndSetDefaults calls [r.CheckAndSetDefaults] if r implements the method. From df861b14f8a3ecc35aa13109dbab7593d038f4e2 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 6 Feb 2025 10:16:30 -0800 Subject: [PATCH 2/3] Add support for `applyResources` Reduce nested switch for bootstrap new type of resources --- lib/auth/autoupdate/autoupdatev1/service.go | 32 +++++++++++++++++++++ lib/auth/init.go | 6 ++++ lib/auth/init_test.go | 3 +- lib/services/local/resource.go | 23 ++++++++------- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/lib/auth/autoupdate/autoupdatev1/service.go b/lib/auth/autoupdate/autoupdatev1/service.go index edc67275f6116..bba80785bc05f 100644 --- a/lib/auth/autoupdate/autoupdatev1/service.go +++ b/lib/auth/autoupdate/autoupdatev1/service.go @@ -60,6 +60,11 @@ type ServiceConfig struct { Emitter apievents.Emitter } +// Backend interface for manipulating AutoUpdate resources. +type Backend interface { + services.AutoUpdateService +} + // Service implements the gRPC API layer for the AutoUpdate. type Service struct { autoupdate.UnimplementedAutoUpdateServiceServer @@ -241,6 +246,21 @@ func (s *Service) UpsertAutoUpdateConfig(ctx context.Context, req *autoupdate.Up return config, trace.Wrap(err) } +// UpsertAutoUpdateConfig creates a new AutoUpdateConfig or forcefully updates an existing AutoUpdateConfig. +// This is a function rather than a method so that it can be used by the gRPC service +// and the auth server init code when dealing with resources to be applied at startup. +func UpsertAutoUpdateConfig( + ctx context.Context, + backend Backend, + config *autoupdate.AutoUpdateConfig, +) (*autoupdate.AutoUpdateConfig, error) { + if err := validateServerSideAgentConfig(config); err != nil { + return nil, trace.Wrap(err, "validating config") + } + out, err := backend.UpsertAutoUpdateConfig(ctx, config) + return out, trace.Wrap(err) +} + // DeleteAutoUpdateConfig deletes AutoUpdateConfig singleton. func (s *Service) DeleteAutoUpdateConfig(ctx context.Context, req *autoupdate.DeleteAutoUpdateConfigRequest) (*emptypb.Empty, error) { authCtx, err := s.authorizer.Authorize(ctx) @@ -435,6 +455,18 @@ func (s *Service) UpsertAutoUpdateVersion(ctx context.Context, req *autoupdate.U return autoUpdateVersion, trace.Wrap(err) } +// UpsertAutoUpdateVersion creates a new AutoUpdateVersion or forcefully updates an existing AutoUpdateVersion. +// This is a function rather than a method so that it can be used by the gRPC service +// and the auth server init code when dealing with resources to be applied at startup. +func UpsertAutoUpdateVersion( + ctx context.Context, + backend Backend, + version *autoupdate.AutoUpdateVersion, +) (*autoupdate.AutoUpdateVersion, error) { + out, err := backend.UpsertAutoUpdateVersion(ctx, version) + return out, trace.Wrap(err) +} + // DeleteAutoUpdateVersion deletes AutoUpdateVersion singleton. func (s *Service) DeleteAutoUpdateVersion(ctx context.Context, req *autoupdate.DeleteAutoUpdateVersionRequest) (*emptypb.Empty, error) { authCtx, err := s.authorizer.Authorize(ctx) diff --git a/lib/auth/init.go b/lib/auth/init.go index 91ca5eaade9d5..b1f549fff923e 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -44,6 +44,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" dbobjectimportrulev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" @@ -52,6 +53,7 @@ import ( apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib" + "github.com/gravitational/teleport/lib/auth/autoupdate/autoupdatev1" "github.com/gravitational/teleport/lib/auth/dbobjectimportrule/dbobjectimportrulev1" "github.com/gravitational/teleport/lib/auth/keystore" "github.com/gravitational/teleport/lib/auth/machineid/machineidv1" @@ -1443,6 +1445,10 @@ func applyResources(ctx context.Context, service *Services, resources []types.Re _, err = machineidv1.UpsertBot(ctx, service, r, time.Now(), "system") case *dbobjectimportrulev1pb.DatabaseObjectImportRule: _, err = dbobjectimportrulev1.UpsertDatabaseObjectImportRule(ctx, service, r) + case *autoupdatev1pb.AutoUpdateConfig: + _, err = autoupdatev1.UpsertAutoUpdateConfig(ctx, service, r) + case *autoupdatev1pb.AutoUpdateVersion: + _, err = autoupdatev1.UpsertAutoUpdateVersion(ctx, service, r) default: return trace.NotImplemented("cannot apply resource of type %T", resource) } diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index 11e279895dc21..872b509844173 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -2284,7 +2284,8 @@ func Test_createPresetDatabaseObjectImportRule(t *testing.T) { } } -// TestInitWithAutoUpdateBootstrap verifies that auth +// TestInitWithAutoUpdateBootstrap verifies that auth init support bootstrapping `AutoUpdateConfig` and +// `AutoUpdateVersion` resources as well as unmarshalling them from yaml configuration. func TestInitWithAutoUpdateBootstrap(t *testing.T) { t.Parallel() diff --git a/lib/services/local/resource.go b/lib/services/local/resource.go index 4e2f60ab2d834..5cdf374fd8cad 100644 --- a/lib/services/local/resource.go +++ b/lib/services/local/resource.go @@ -72,7 +72,15 @@ func itemsFromResource(resource types.Resource) ([]backend.Item, error) { var item *backend.Item var extItems []backend.Item var err error - switch r := resource.(type) { + + // Unwrap "new style" resources. + // We always want to switch over the underlying type. + var res any = resource + if w, ok := res.(types.Resource153Unwrapper); ok { + res = w.Unwrap() + } + + switch r := res.(type) { case types.User: item, err = itemFromUser(r) if auth := r.GetLocalAuth(); err == nil && auth != nil { @@ -98,15 +106,10 @@ func itemsFromResource(resource types.Resource) ([]backend.Item, error) { item, err = itemFromClusterNetworkingConfig(r) case types.AuthPreference: item, err = itemFromAuthPreference(r) - case types.Resource153Unwrapper: - switch r153 := r.Unwrap().(type) { - case *autoupdatev1pb.AutoUpdateConfig: - item, err = itemFromAutoUpdateConfig(r153) - case *autoupdatev1pb.AutoUpdateVersion: - item, err = itemFromAutoUpdateVersion(r153) - default: - return nil, trace.NotImplemented("cannot itemFrom resource of type %T", resource) - } + case *autoupdatev1pb.AutoUpdateConfig: + item, err = itemFromAutoUpdateConfig(r) + case *autoupdatev1pb.AutoUpdateVersion: + item, err = itemFromAutoUpdateVersion(r) default: return nil, trace.NotImplemented("cannot itemFrom resource of type %T", resource) } From 1568a4a9aedeeac109456793c911391431145255 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 6 Feb 2025 10:52:28 -0800 Subject: [PATCH 3/3] Transform test to table test to check same resources for both `bootstrapResources` and `applyResources` --- lib/auth/init_test.go | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index 872b509844173..8ecb060a409e9 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -2284,9 +2284,10 @@ func Test_createPresetDatabaseObjectImportRule(t *testing.T) { } } -// TestInitWithAutoUpdateBootstrap verifies that auth init support bootstrapping `AutoUpdateConfig` and -// `AutoUpdateVersion` resources as well as unmarshalling them from yaml configuration. -func TestInitWithAutoUpdateBootstrap(t *testing.T) { +// TestInitWithAutoUpdateResources verifies that auth init support bootstrapping and apply +// `AutoUpdateConfig` and `AutoUpdateVersion` resources as well as unmarshalling them from +// yaml configuration. +func TestInitWithAutoUpdateResources(t *testing.T) { t.Parallel() const autoUpdateConfigYAML = `kind: autoupdate_config @@ -2305,21 +2306,31 @@ spec: version: v1` ctx := context.Background() - - cfg := setupConfig(t) - cfg.BootstrapResources = []types.Resource{ + resources := []types.Resource{ resourceFromYAML(t, autoUpdateConfigYAML), resourceFromYAML(t, autoUpdateVersionYAML), } - auth, err := Init(ctx, cfg) - require.NoError(t, err) + for _, test := range []struct { + name string + fn func(cfg *InitConfig) + }{ + {name: "bootstrap", fn: func(cfg *InitConfig) { cfg.BootstrapResources = resources }}, + {name: "apply", fn: func(cfg *InitConfig) { cfg.ApplyOnStartupResources = resources }}, + } { + t.Run(test.name, func(t *testing.T) { + cfg := setupConfig(t) + test.fn(&cfg) + auth, err := Init(ctx, cfg) + require.NoError(t, err) - config, err := auth.GetAutoUpdateConfig(ctx) - assert.NoError(t, err) - assert.Equal(t, "enabled", config.GetSpec().GetTools().GetMode()) + config, err := auth.GetAutoUpdateConfig(ctx) + assert.NoError(t, err) + assert.Equal(t, "enabled", config.GetSpec().GetTools().GetMode()) - version, err := auth.GetAutoUpdateVersion(ctx) - assert.NoError(t, err) - assert.Equal(t, "1.2.3", version.GetSpec().GetTools().GetTargetVersion()) + version, err := auth.GetAutoUpdateVersion(ctx) + assert.NoError(t, err) + assert.Equal(t, "1.2.3", version.GetSpec().GetTools().GetTargetVersion()) + }) + } }