From 778a93c54edf78eadad6f734be5f6a3b94484ab3 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 12 Sep 2024 12:46:13 -0400 Subject: [PATCH 1/3] Add gRPC service implementation --- lib/auth/auth.go | 8 + lib/auth/autoupdate/v1/service.go | 252 +++++++++++++++++++++++ lib/auth/autoupdate/v1/service_test.go | 270 +++++++++++++++++++++++++ lib/auth/grpcserver.go | 13 +- lib/auth/init.go | 3 + lib/authz/permissions.go | 2 + lib/service/service.go | 1 + lib/service/servicecfg/config.go | 3 + lib/services/local/autoupdate.go | 30 +-- lib/services/local/autoupdate_test.go | 8 +- 10 files changed, 570 insertions(+), 20 deletions(-) create mode 100644 lib/auth/autoupdate/v1/service.go create mode 100644 lib/auth/autoupdate/v1/service_test.go diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 7efbb7407885..78c210174c9e 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -200,6 +200,12 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { } cfg.ClusterConfiguration = clusterConfig } + if cfg.AutoUpdateService == nil { + cfg.AutoUpdateService, err = local.NewAutoUpdateService(cfg.Backend) + if err != nil { + return nil, trace.Wrap(err) + } + } if cfg.Restrictions == nil { cfg.Restrictions = local.NewRestrictionsService(cfg.Backend) } @@ -410,6 +416,7 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { Access: cfg.Access, DynamicAccessExt: cfg.DynamicAccessExt, ClusterConfiguration: cfg.ClusterConfiguration, + AutoUpdateService: cfg.AutoUpdateService, Restrictions: cfg.Restrictions, Apps: cfg.Apps, Kubernetes: cfg.Kubernetes, @@ -645,6 +652,7 @@ type Services struct { services.DevicesGetter services.SPIFFEFederations services.StaticHostUser + services.AutoUpdateService } // GetWebSession returns existing web session described by req. diff --git a/lib/auth/autoupdate/v1/service.go b/lib/auth/autoupdate/v1/service.go new file mode 100644 index 000000000000..ece6c93eb110 --- /dev/null +++ b/lib/auth/autoupdate/v1/service.go @@ -0,0 +1,252 @@ +/* + * 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 . + */ + +package autoupdate + +import ( + "context" + + "github.com/gravitational/trace" + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/services" +) + +// Cache defines only read-only service methods. +type Cache interface { + // GetAutoUpdateConfig gets the autoupdate configuration from the backend. + GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) + + // GetAutoUpdateVersion gets the autoupdate version from the backend. + GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) +} + +// ServiceConfig holds configuration options for the autoupdate gRPC service. +type ServiceConfig struct { + // Authorizer is the authorizer used to check access to resources. + Authorizer authz.Authorizer + // Backend is the backend used to store autoupdate resources. + Backend services.AutoUpdateService + // Cache is the cache used to store autoupdate resources. + Cache Cache +} + +// Service implements the gRPC API layer for the Autoupdate. +type Service struct { + autoupdate.UnimplementedAutoUpdateServiceServer + + authorizer authz.Authorizer + backend services.AutoUpdateService + cache Cache +} + +// NewService returns a new Autoupdate API service using the given storage layer and authorizer. +func NewService(cfg ServiceConfig) (*Service, error) { + switch { + case cfg.Backend == nil: + return nil, trace.BadParameter("backend is required") + case cfg.Authorizer == nil: + return nil, trace.BadParameter("authorizer is required") + case cfg.Cache == nil: + return nil, trace.BadParameter("cache is required") + } + return &Service{ + authorizer: cfg.Authorizer, + backend: cfg.Backend, + cache: cfg.Cache, + }, nil +} + +// GetAutoUpdateConfig gets the current autoupdate config singleton. +func (s *Service) GetAutoUpdateConfig(ctx context.Context, req *autoupdate.GetAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { + config, err := s.cache.GetAutoUpdateConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + return config, nil +} + +// CreateAutoUpdateConfig creates autoupdate config singleton. +func (s *Service) CreateAutoUpdateConfig(ctx context.Context, req *autoupdate.CreateAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateConfig, types.VerbCreate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + config, err := s.backend.CreateAutoUpdateConfig(ctx, req.Config) + return config, trace.Wrap(err) +} + +// UpdateAutoUpdateConfig updates autoupdate config singleton. +func (s *Service) UpdateAutoUpdateConfig(ctx context.Context, req *autoupdate.UpdateAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateConfig, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + config, err := s.backend.UpdateAutoUpdateConfig(ctx, req.Config) + return config, trace.Wrap(err) +} + +// UpsertAutoUpdateConfig updates or creates autoupdate config singleton. +func (s *Service) UpsertAutoUpdateConfig(ctx context.Context, req *autoupdate.UpsertAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateConfig, types.VerbCreate, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + config, err := s.backend.UpsertAutoUpdateConfig(ctx, req.Config) + return config, trace.Wrap(err) +} + +// DeleteAutoUpdateConfig deletes autoupdate config singleton. +func (s *Service) DeleteAutoUpdateConfig(ctx context.Context, req *autoupdate.DeleteAutoUpdateConfigRequest) (*emptypb.Empty, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateConfig, types.VerbDelete); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if err := s.backend.DeleteAutoUpdateConfig(ctx); err != nil { + return nil, trace.Wrap(err) + } + return &emptypb.Empty{}, nil +} + +// GetAutoUpdateVersion gets the current autoupdate version singleton. +func (s *Service) GetAutoUpdateVersion(ctx context.Context, req *autoupdate.GetAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { + version, err := s.cache.GetAutoUpdateVersion(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + return version, nil +} + +// CreateAutoUpdateVersion creates autoupdate version singleton. +func (s *Service) CreateAutoUpdateVersion(ctx context.Context, req *autoupdate.CreateAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateVersion, types.VerbCreate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + autoupdateVersion, err := s.backend.CreateAutoUpdateVersion(ctx, req.Version) + return autoupdateVersion, trace.Wrap(err) +} + +// UpdateAutoUpdateVersion updates autoupdate version singleton. +func (s *Service) UpdateAutoUpdateVersion(ctx context.Context, req *autoupdate.UpdateAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateVersion, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + autoupdateVersion, err := s.backend.UpdateAutoUpdateVersion(ctx, req.Version) + return autoupdateVersion, trace.Wrap(err) +} + +// UpsertAutoUpdateVersion updates or creates autoupdate version singleton. +func (s *Service) UpsertAutoUpdateVersion(ctx context.Context, req *autoupdate.UpsertAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateVersion, types.VerbCreate, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil { + return nil, trace.Wrap(err) + } + + autoupdateVersion, err := s.backend.UpsertAutoUpdateVersion(ctx, req.Version) + return autoupdateVersion, trace.Wrap(err) +} + +// DeleteAutoUpdateVersion deletes autoupdate version singleton. +func (s *Service) DeleteAutoUpdateVersion(ctx context.Context, req *autoupdate.DeleteAutoUpdateVersionRequest) (*emptypb.Empty, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateVersion, types.VerbDelete); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if err := s.backend.DeleteAutoUpdateVersion(ctx); err != nil { + return nil, trace.Wrap(err) + } + return &emptypb.Empty{}, nil +} diff --git a/lib/auth/autoupdate/v1/service_test.go b/lib/auth/autoupdate/v1/service_test.go new file mode 100644 index 000000000000..5e1606cac198 --- /dev/null +++ b/lib/auth/autoupdate/v1/service_test.go @@ -0,0 +1,270 @@ +// 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 . + +package autoupdate + +import ( + "context" + "fmt" + "slices" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/backend/memory" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/local" + "github.com/gravitational/teleport/lib/utils" +) + +var allAdminStates = map[authz.AdminActionAuthState]string{ + authz.AdminActionAuthUnauthorized: "Unauthorized", + authz.AdminActionAuthNotRequired: "NotRequired", + authz.AdminActionAuthMFAVerified: "MFAVerified", + authz.AdminActionAuthMFAVerifiedWithReuse: "MFAVerifiedWithReuse", +} + +func stateToString(state authz.AdminActionAuthState) string { + str, ok := allAdminStates[state] + if !ok { + return fmt.Sprintf("unknown(%v)", state) + } + return str +} + +// otherAdminStates returns all admin states except for those passed in +func otherAdminStates(states []authz.AdminActionAuthState) []authz.AdminActionAuthState { + var out []authz.AdminActionAuthState + for state := range allAdminStates { + found := slices.Index(states, state) != -1 + if !found { + out = append(out, state) + } + } + return out +} + +// callMethod calls a method with given name in the DatabaseObjectService service +func callMethod(t *testing.T, service *Service, method string) error { + for _, desc := range autoupdate.AutoUpdateService_ServiceDesc.Methods { + if desc.MethodName == method { + _, err := desc.Handler(service, context.Background(), func(_ any) error { return nil }, nil) + return err + } + } + require.FailNow(t, "method %v not found", method) + panic("this line should never be reached: FailNow() should interrupt the test") +} + +func TestServiceAccess(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + allowedVerbs []string + allowedStates []authz.AdminActionAuthState + disallowedStates []authz.AdminActionAuthState + }{ + { + name: "CreateAutoUpdateConfig", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbCreate}, + }, + { + name: "UpdateAutoUpdateConfig", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbUpdate}, + }, + { + name: "UpsertAutoUpdateConfig", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbUpdate, types.VerbCreate}, + }, + { + name: "GetAutoUpdateConfig", + allowedStates: []authz.AdminActionAuthState{}, + disallowedStates: []authz.AdminActionAuthState{}, + allowedVerbs: []string{types.VerbRead}, + }, + { + name: "DeleteAutoUpdateConfig", + allowedStates: []authz.AdminActionAuthState{authz.AdminActionAuthNotRequired, authz.AdminActionAuthMFAVerified}, + allowedVerbs: []string{types.VerbDelete}, + }, + // AutoUpdate version check. + { + name: "CreateAutoUpdateVersion", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbCreate}, + }, + { + name: "UpdateAutoUpdateVersion", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbUpdate}, + }, + { + name: "UpsertAutoUpdateVersion", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbUpdate, types.VerbCreate}, + }, + { + name: "GetAutoUpdateVersion", + allowedStates: []authz.AdminActionAuthState{}, + disallowedStates: []authz.AdminActionAuthState{}, + allowedVerbs: []string{types.VerbRead}, + }, + { + name: "DeleteAutoUpdateVersion", + allowedStates: []authz.AdminActionAuthState{authz.AdminActionAuthNotRequired, authz.AdminActionAuthMFAVerified}, + allowedVerbs: []string{types.VerbDelete}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + // test the method with allowed admin states, each one separately. + t.Run("allowed admin states", func(t *testing.T) { + for _, state := range tt.allowedStates { + t.Run(stateToString(state), func(t *testing.T) { + for _, verbs := range utils.Combinations(tt.allowedVerbs) { + t.Run(fmt.Sprintf("verbs=%v", verbs), func(t *testing.T) { + service := newService(t, state, fakeChecker{allowedVerbs: verbs}) + err := callMethod(t, service, tt.name) + // expect access denied except with full set of verbs. + if len(verbs) == len(tt.allowedVerbs) { + require.False(t, trace.IsAccessDenied(err)) + } else { + require.True(t, trace.IsAccessDenied(err), "expected access denied for verbs %v, got err=%v", verbs, err) + } + }) + } + }) + } + }) + + // test the method with disallowed admin states; expect failures. + t.Run("disallowed admin states", func(t *testing.T) { + disallowedStates := otherAdminStates(tt.allowedStates) + if tt.disallowedStates != nil { + disallowedStates = tt.disallowedStates + } + for _, state := range disallowedStates { + t.Run(stateToString(state), func(t *testing.T) { + // it is enough to test against tt.allowedVerbs, + // this is the only different data point compared to the test cases above. + service := newService(t, state, fakeChecker{allowedVerbs: tt.allowedVerbs}) + err := callMethod(t, service, tt.name) + require.True(t, trace.IsAccessDenied(err)) + }) + } + }) + }) + } + + // verify that all declared methods have matching test cases + t.Run("verify coverage", func(t *testing.T) { + for _, method := range autoupdate.AutoUpdateService_ServiceDesc.Methods { + t.Run(method.MethodName, func(t *testing.T) { + match := false + for _, testCase := range testCases { + match = match || testCase.name == method.MethodName + } + require.True(t, match, "method %v without coverage, no matching tests", method.MethodName) + }) + } + }) +} + +type fakeChecker struct { + allowedVerbs []string + services.AccessChecker +} + +func (f fakeChecker) CheckAccessToRule(_ services.RuleContext, _ string, resource string, verb string) error { + if resource == types.KindAutoUpdateConfig || resource == types.KindAutoUpdateVersion { + for _, allowedVerb := range f.allowedVerbs { + if allowedVerb == verb { + return nil + } + } + } + + return trace.AccessDenied("access denied to rule=%v/verb=%v", resource, verb) +} + +func newService(t *testing.T, authState authz.AdminActionAuthState, checker services.AccessChecker) *Service { + t.Helper() + + bk, err := memory.New(memory.Config{}) + require.NoError(t, err) + + storage, err := local.NewAutoUpdateService(bk) + require.NoError(t, err) + + return newServiceWithStorage(t, authState, checker, storage) +} + +func newServiceWithStorage(t *testing.T, authState authz.AdminActionAuthState, checker services.AccessChecker, storage services.AutoUpdateService) *Service { + t.Helper() + + authorizer := authz.AuthorizerFunc(func(ctx context.Context) (*authz.Context, error) { + user, err := types.NewUser("alice") + if err != nil { + return nil, err + } + return &authz.Context{ + User: user, + Checker: checker, + AdminActionAuthState: authState, + }, nil + }) + + service, err := NewService(ServiceConfig{ + Authorizer: authorizer, + Backend: storage, + Cache: storage, + }) + require.NoError(t, err) + return service +} diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 83799e4f1370..d314b64bd85c 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -51,6 +51,7 @@ import ( "github.com/gravitational/teleport/api/constants" accessmonitoringrules "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" + autoupdatepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -78,6 +79,7 @@ import ( "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/lib/accessmonitoringrules/accessmonitoringrulesv1" "github.com/gravitational/teleport/lib/auth/authclient" + autoupdatev1 "github.com/gravitational/teleport/lib/auth/autoupdate/v1" "github.com/gravitational/teleport/lib/auth/clusterconfig/clusterconfigv1" "github.com/gravitational/teleport/lib/auth/crownjewel/crownjewelv1" "github.com/gravitational/teleport/lib/auth/dbobject/dbobjectv1" @@ -508,7 +510,6 @@ func WatchEvents(watch *authpb.Watch, stream WatchEvent, componentName string, a Kinds: watch.Kinds, AllowPartialSuccess: watch.AllowPartialSuccess, } - events, err := auth.NewStream(stream.Context(), servicesWatch) if err != nil { return trace.Wrap(err) @@ -5435,6 +5436,16 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { } userprovisioningpb.RegisterStaticHostUsersServiceServer(server, staticHostUserServer) + autoUpdateServiceServer, err := autoupdatev1.NewService(autoupdatev1.ServiceConfig{ + Authorizer: cfg.Authorizer, + Backend: cfg.AuthServer.Services, + Cache: cfg.AuthServer.Services, + }) + if err != nil { + return nil, trace.Wrap(err) + } + autoupdatepb.RegisterAutoUpdateServiceServer(server, autoUpdateServiceServer) + // Only register the service if this is an open source build. Enterprise builds // register the actual service via an auth plugin, if we register here then all // Enterprise builds would fail with a duplicate service registered error. diff --git a/lib/auth/init.go b/lib/auth/init.go index 974ae19f13cd..e9e1fbeffc6e 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -158,6 +158,9 @@ type InitConfig struct { // ClusterConfiguration is a services that holds cluster wide configuration. ClusterConfiguration services.ClusterConfiguration + // AutoUpdateService is a service of autoupdate configuration and version. + AutoUpdateService services.AutoUpdateService + // Restrictions is a service to access network restrictions, etc Restrictions services.Restrictions diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index a235e11b8edb..de3c1c070c1b 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -913,6 +913,8 @@ func roleSpecForProxy(clusterName string) types.RoleSpecV6 { types.NewRule(types.KindSAMLIdPServiceProvider, services.RO()), types.NewRule(types.KindUserGroup, services.RO()), types.NewRule(types.KindClusterMaintenanceConfig, services.RO()), + types.NewRule(types.KindAutoUpdateConfig, services.RO()), + types.NewRule(types.KindAutoUpdateVersion, services.RO()), types.NewRule(types.KindIntegration, append(services.RO(), types.VerbUse)), types.NewRule(types.KindAuditQuery, services.RO()), types.NewRule(types.KindSecurityReport, services.RO()), diff --git a/lib/service/service.go b/lib/service/service.go index f20c5ad9ef94..b1b49a6f5375 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2063,6 +2063,7 @@ func (process *TeleportProcess) initAuthService() error { VersionStorage: process.storage, Authority: cfg.Keygen, ClusterConfiguration: cfg.ClusterConfiguration, + AutoUpdateService: cfg.AutoUpdateService, ClusterAuditConfig: cfg.Auth.AuditConfig, ClusterNetworkingConfig: cfg.Auth.NetworkingConfig, SessionRecordingConfig: cfg.Auth.SessionRecordingConfig, diff --git a/lib/service/servicecfg/config.go b/lib/service/servicecfg/config.go index 33a89c6223fb..8a769ace3822 100644 --- a/lib/service/servicecfg/config.go +++ b/lib/service/servicecfg/config.go @@ -169,6 +169,9 @@ type Config struct { // ClusterConfiguration is a service that provides cluster configuration ClusterConfiguration services.ClusterConfiguration + // AutoUpdateService is a service that provides auto update configuration and version. + AutoUpdateService services.AutoUpdateService + // CipherSuites is a list of TLS ciphersuites that Teleport supports. If // omitted, a Teleport selected list of defaults will be used. CipherSuites []uint16 diff --git a/lib/services/local/autoupdate.go b/lib/services/local/autoupdate.go index 21b4172767cc..a1a04a38ea65 100644 --- a/lib/services/local/autoupdate.go +++ b/lib/services/local/autoupdate.go @@ -36,14 +36,14 @@ const ( autoUpdateVersionPrefix = "auto_update_version" ) -// AutoupdateService is responsible for managing auto update configuration and version. -type AutoupdateService struct { +// AutoUpdateService is responsible for managing auto update configuration and version. +type AutoUpdateService struct { config *generic.ServiceWrapper[*autoupdate.AutoUpdateConfig] version *generic.ServiceWrapper[*autoupdate.AutoUpdateVersion] } -// NewAutoupdateService returns a new AutoupdateService. -func NewAutoupdateService(backend backend.Backend) (*AutoupdateService, error) { +// NewAutoUpdateService returns a new AutoUpdateService. +func NewAutoUpdateService(backend backend.Backend) (*AutoUpdateService, error) { config, err := generic.NewServiceWrapper( generic.ServiceWrapperConfig[*autoupdate.AutoUpdateConfig]{ Backend: backend, @@ -75,14 +75,14 @@ func NewAutoupdateService(backend backend.Backend) (*AutoupdateService, error) { return nil, trace.Wrap(err) } - return &AutoupdateService{ + return &AutoUpdateService{ config: config, version: version, }, nil } // CreateAutoUpdateConfig creates an auto update configuration singleton. -func (s *AutoupdateService) CreateAutoUpdateConfig( +func (s *AutoUpdateService) CreateAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, ) (*autoupdate.AutoUpdateConfig, error) { @@ -91,7 +91,7 @@ func (s *AutoupdateService) CreateAutoUpdateConfig( } // UpdateAutoUpdateConfig updates an auto update configuration singleton. -func (s *AutoupdateService) UpdateAutoUpdateConfig( +func (s *AutoUpdateService) UpdateAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, ) (*autoupdate.AutoUpdateConfig, error) { @@ -100,7 +100,7 @@ func (s *AutoupdateService) UpdateAutoUpdateConfig( } // UpsertAutoUpdateConfig sets an auto update configuration. -func (s *AutoupdateService) UpsertAutoUpdateConfig( +func (s *AutoUpdateService) UpsertAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, ) (*autoupdate.AutoUpdateConfig, error) { @@ -109,18 +109,18 @@ func (s *AutoupdateService) UpsertAutoUpdateConfig( } // GetAutoUpdateConfig gets the auto update configuration from the backend. -func (s *AutoupdateService) GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) { +func (s *AutoUpdateService) GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) { config, err := s.config.GetResource(ctx, types.MetaNameAutoUpdateConfig) return config, trace.Wrap(err) } // DeleteAutoUpdateConfig deletes the auto update configuration from the backend. -func (s *AutoupdateService) DeleteAutoUpdateConfig(ctx context.Context) error { +func (s *AutoUpdateService) DeleteAutoUpdateConfig(ctx context.Context) error { return trace.Wrap(s.config.DeleteResource(ctx, types.MetaNameAutoUpdateConfig)) } // CreateAutoUpdateVersion creates an autoupdate version resource. -func (s *AutoupdateService) CreateAutoUpdateVersion( +func (s *AutoUpdateService) CreateAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, ) (*autoupdate.AutoUpdateVersion, error) { @@ -129,7 +129,7 @@ func (s *AutoupdateService) CreateAutoUpdateVersion( } // UpdateAutoUpdateVersion updates an autoupdate version resource. -func (s *AutoupdateService) UpdateAutoUpdateVersion( +func (s *AutoUpdateService) UpdateAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, ) (*autoupdate.AutoUpdateVersion, error) { @@ -138,7 +138,7 @@ func (s *AutoupdateService) UpdateAutoUpdateVersion( } // UpsertAutoUpdateVersion sets autoupdate version resource. -func (s *AutoupdateService) UpsertAutoUpdateVersion( +func (s *AutoUpdateService) UpsertAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, ) (*autoupdate.AutoUpdateVersion, error) { @@ -147,12 +147,12 @@ func (s *AutoupdateService) UpsertAutoUpdateVersion( } // GetAutoUpdateVersion gets the auto update version from the backend. -func (s *AutoupdateService) GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) { +func (s *AutoUpdateService) GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) { version, err := s.version.GetResource(ctx, types.MetaNameAutoUpdateVersion) return version, trace.Wrap(err) } // DeleteAutoUpdateVersion deletes the auto update version from the backend. -func (s *AutoupdateService) DeleteAutoUpdateVersion(ctx context.Context) error { +func (s *AutoUpdateService) DeleteAutoUpdateVersion(ctx context.Context) error { return trace.Wrap(s.version.DeleteResource(ctx, types.MetaNameAutoUpdateVersion)) } diff --git a/lib/services/local/autoupdate_test.go b/lib/services/local/autoupdate_test.go index a27af676589d..0a6f4e5bd2b8 100644 --- a/lib/services/local/autoupdate_test.go +++ b/lib/services/local/autoupdate_test.go @@ -43,7 +43,7 @@ func TestAutoUpdateServiceConfigCRUD(t *testing.T) { bk, err := memory.New(memory.Config{}) require.NoError(t, err) - service, err := NewAutoupdateService(bk) + service, err := NewAutoUpdateService(bk) require.NoError(t, err) ctx := context.Background() @@ -99,7 +99,7 @@ func TestAutoUpdateServiceVersionCRUD(t *testing.T) { bk, err := memory.New(memory.Config{}) require.NoError(t, err) - service, err := NewAutoupdateService(bk) + service, err := NewAutoUpdateService(bk) require.NoError(t, err) ctx := context.Background() @@ -155,7 +155,7 @@ func TestAutoUpdateServiceInvalidNameCreate(t *testing.T) { bk, err := memory.New(memory.Config{}) require.NoError(t, err) - service, err := NewAutoupdateService(bk) + service, err := NewAutoUpdateService(bk) require.NoError(t, err) ctx := context.Background() @@ -190,7 +190,7 @@ func TestAutoUpdateServiceInvalidNameUpdate(t *testing.T) { bk, err := memory.New(memory.Config{}) require.NoError(t, err) - service, err := NewAutoupdateService(bk) + service, err := NewAutoUpdateService(bk) require.NoError(t, err) ctx := context.Background() From 99221d77c28e0f8f8ff4bee9479f7f8979e8aea6 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Thu, 12 Sep 2024 19:54:32 -0400 Subject: [PATCH 2/3] CR changes --- lib/auth/autoupdate/{v1 => autoupdatev1}/service.go | 2 +- lib/auth/autoupdate/{v1 => autoupdatev1}/service_test.go | 2 +- lib/auth/grpcserver.go | 4 ++-- lib/authz/permissions.go | 2 -- 4 files changed, 4 insertions(+), 6 deletions(-) rename lib/auth/autoupdate/{v1 => autoupdatev1}/service.go (99%) rename lib/auth/autoupdate/{v1 => autoupdatev1}/service_test.go (99%) diff --git a/lib/auth/autoupdate/v1/service.go b/lib/auth/autoupdate/autoupdatev1/service.go similarity index 99% rename from lib/auth/autoupdate/v1/service.go rename to lib/auth/autoupdate/autoupdatev1/service.go index ece6c93eb110..576d23b50200 100644 --- a/lib/auth/autoupdate/v1/service.go +++ b/lib/auth/autoupdate/autoupdatev1/service.go @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package autoupdate +package autoupdatev1 import ( "context" diff --git a/lib/auth/autoupdate/v1/service_test.go b/lib/auth/autoupdate/autoupdatev1/service_test.go similarity index 99% rename from lib/auth/autoupdate/v1/service_test.go rename to lib/auth/autoupdate/autoupdatev1/service_test.go index 5e1606cac198..f162072c0b3e 100644 --- a/lib/auth/autoupdate/v1/service_test.go +++ b/lib/auth/autoupdate/autoupdatev1/service_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package autoupdate +package autoupdatev1 import ( "context" diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index d314b64bd85c..37296b19e585 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -51,7 +51,7 @@ import ( "github.com/gravitational/teleport/api/constants" accessmonitoringrules "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" - autoupdatepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -5444,7 +5444,7 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { if err != nil { return nil, trace.Wrap(err) } - autoupdatepb.RegisterAutoUpdateServiceServer(server, autoUpdateServiceServer) + autoupdatev1pb.RegisterAutoUpdateServiceServer(server, autoUpdateServiceServer) // Only register the service if this is an open source build. Enterprise builds // register the actual service via an auth plugin, if we register here then all diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index de3c1c070c1b..a235e11b8edb 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -913,8 +913,6 @@ func roleSpecForProxy(clusterName string) types.RoleSpecV6 { types.NewRule(types.KindSAMLIdPServiceProvider, services.RO()), types.NewRule(types.KindUserGroup, services.RO()), types.NewRule(types.KindClusterMaintenanceConfig, services.RO()), - types.NewRule(types.KindAutoUpdateConfig, services.RO()), - types.NewRule(types.KindAutoUpdateVersion, services.RO()), types.NewRule(types.KindIntegration, append(services.RO(), types.VerbUse)), types.NewRule(types.KindAuditQuery, services.RO()), types.NewRule(types.KindSecurityReport, services.RO()), From 28963035ac39b9c07a8bb8ef5ebd8b593b1cb12b Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Mon, 16 Sep 2024 12:50:53 -0400 Subject: [PATCH 3/3] Add auth for read operation --- lib/auth/autoupdate/autoupdatev1/service.go | 18 ++++++++++++++ .../autoupdate/autoupdatev1/service_test.go | 24 ++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/auth/autoupdate/autoupdatev1/service.go b/lib/auth/autoupdate/autoupdatev1/service.go index 576d23b50200..555a03506d55 100644 --- a/lib/auth/autoupdate/autoupdatev1/service.go +++ b/lib/auth/autoupdate/autoupdatev1/service.go @@ -77,6 +77,15 @@ func NewService(cfg ServiceConfig) (*Service, error) { // GetAutoUpdateConfig gets the current autoupdate config singleton. func (s *Service) GetAutoUpdateConfig(ctx context.Context, req *autoupdate.GetAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateConfig, types.VerbRead); err != nil { + return nil, trace.Wrap(err) + } + config, err := s.cache.GetAutoUpdateConfig(ctx) if err != nil { return nil, trace.Wrap(err) @@ -165,6 +174,15 @@ func (s *Service) DeleteAutoUpdateConfig(ctx context.Context, req *autoupdate.De // GetAutoUpdateVersion gets the current autoupdate version singleton. func (s *Service) GetAutoUpdateVersion(ctx context.Context, req *autoupdate.GetAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindAutoUpdateVersion, types.VerbRead); err != nil { + return nil, trace.Wrap(err) + } + version, err := s.cache.GetAutoUpdateVersion(ctx) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/autoupdate/autoupdatev1/service_test.go b/lib/auth/autoupdate/autoupdatev1/service_test.go index f162072c0b3e..840fd9bbf94c 100644 --- a/lib/auth/autoupdate/autoupdatev1/service_test.go +++ b/lib/auth/autoupdate/autoupdatev1/service_test.go @@ -110,10 +110,14 @@ func TestServiceAccess(t *testing.T) { allowedVerbs: []string{types.VerbUpdate, types.VerbCreate}, }, { - name: "GetAutoUpdateConfig", - allowedStates: []authz.AdminActionAuthState{}, - disallowedStates: []authz.AdminActionAuthState{}, - allowedVerbs: []string{types.VerbRead}, + name: "GetAutoUpdateConfig", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthUnauthorized, + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbRead}, }, { name: "DeleteAutoUpdateConfig", @@ -149,10 +153,14 @@ func TestServiceAccess(t *testing.T) { allowedVerbs: []string{types.VerbUpdate, types.VerbCreate}, }, { - name: "GetAutoUpdateVersion", - allowedStates: []authz.AdminActionAuthState{}, - disallowedStates: []authz.AdminActionAuthState{}, - allowedVerbs: []string{types.VerbRead}, + name: "GetAutoUpdateVersion", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthUnauthorized, + authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, + authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbRead}, }, { name: "DeleteAutoUpdateVersion",