diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 912f04081d305..a55ac85ee0387 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -4341,6 +4341,10 @@ func (a *ServerWithRoles) SetAuthPreference(ctx context.Context, newAuthPref typ return trace.Wrap(err) } + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + // check that the given RequireMFAType is supported in this build. if newAuthPref.GetPrivateKeyPolicy().IsHardwareKeyPolicy() { if modules.GetModules().BuildType() != modules.BuildEnterprise { @@ -4369,6 +4373,10 @@ func (a *ServerWithRoles) ResetAuthPreference(ctx context.Context) error { return trace.Wrap(err) } + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + return a.authServer.SetAuthPreference(ctx, types.DefaultAuthPreference()) } @@ -4405,6 +4413,10 @@ func (a *ServerWithRoles) SetClusterNetworkingConfig(ctx context.Context, newNet } } + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + tst, err := newNetConfig.GetTunnelStrategyType() if err != nil { return trace.Wrap(err) @@ -4433,6 +4445,10 @@ func (a *ServerWithRoles) ResetClusterNetworkingConfig(ctx context.Context) erro } } + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + return a.authServer.SetClusterNetworkingConfig(ctx, types.DefaultClusterNetworkingConfig()) } @@ -4459,6 +4475,10 @@ func (a *ServerWithRoles) SetSessionRecordingConfig(ctx context.Context, newRecC } } + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + return a.authServer.SetSessionRecordingConfig(ctx, newRecConfig) } @@ -4478,6 +4498,10 @@ func (a *ServerWithRoles) ResetSessionRecordingConfig(ctx context.Context) error } } + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + return a.authServer.SetSessionRecordingConfig(ctx, types.DefaultSessionRecordingConfig()) } @@ -5370,6 +5394,11 @@ func (a *ServerWithRoles) SetNetworkRestrictions(ctx context.Context, nr types.N if err := a.action(apidefaults.Namespace, types.KindNetworkRestrictions, types.VerbCreate, types.VerbUpdate); err != nil { return trace.Wrap(err) } + + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + return a.authServer.SetNetworkRestrictions(ctx, nr) } @@ -5378,6 +5407,11 @@ func (a *ServerWithRoles) DeleteNetworkRestrictions(ctx context.Context) error { if err := a.action(apidefaults.Namespace, types.KindNetworkRestrictions, types.VerbDelete); err != nil { return trace.Wrap(err) } + + if err := authz.AuthorizeAdminAction(ctx, &a.context); err != nil { + return trace.Wrap(err) + } + return a.authServer.DeleteNetworkRestrictions(ctx) } diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index 2c909c2ba30d7..88b72c2961b70 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -1813,6 +1813,7 @@ func serverWithAllowRules(t *testing.T, srv *TestAuthServer, allowRules []types. localUser := authz.LocalUser{Username: username, Identity: tlsca.Identity{Username: username}} authContext, err := authz.ContextForLocalUser(ctx, localUser, srv.AuthServer, srv.ClusterName, true /* disableDeviceAuthz */) require.NoError(t, err) + authContext.AdminActionAuthorized = true return &ServerWithRoles{ authServer: srv.AuthServer, diff --git a/tool/tctl/common/admin_action_test.go b/tool/tctl/common/admin_action_test.go index 8c9622034c760..eac3f630a0943 100644 --- a/tool/tctl/common/admin_action_test.go +++ b/tool/tctl/common/admin_action_test.go @@ -68,6 +68,10 @@ func TestAdminActionMFA(t *testing.T) { t.Run("SAMLConnector", s.testSAMLConnector) t.Run("GithubConnector", s.testGithubConnector) t.Run("SAMLIdpServiceProvider", s.testSAMLIdpServiceProvider) + t.Run("ClusterAuthPreference", s.testClusterAuthPreference) + t.Run("NetworkRestriction", s.testNetworkRestriction) + t.Run("NetworkingConfig", s.testNetworkingConfig) + t.Run("SessionRecordingConfig", s.testSessionRecordingConfig) } func (s *adminActionTestSuite) testUsers(t *testing.T) { @@ -116,9 +120,9 @@ func (s *adminActionTestSuite) testUsers(t *testing.T) { } s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: user, - resourceCreate: createUser, - resourceDelete: deleteUser, + resource: user, + resourceCreate: createUser, + resourceCleanup: deleteUser, }) } @@ -190,16 +194,16 @@ func (s *adminActionTestSuite) testRoles(t *testing.T) { } s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: role, - resourceCreate: createRole, - resourceDelete: deleteRole, + resource: role, + resourceCreate: createRole, + resourceCleanup: deleteRole, }) s.testEditCommand(t, ctx, editCommandTestCase{ - resourceRef: getResourceRef(role), - resourceCreate: createRole, - resourceGet: getRole, - resourceDelete: deleteRole, + resourceRef: getResourceRef(role), + resourceCreate: createRole, + resourceGet: getRole, + resourceCleanup: deleteRole, }) } @@ -335,18 +339,18 @@ func (s *adminActionTestSuite) testTokens(t *testing.T) { t.Run("ResourceCommands", func(t *testing.T) { s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: token, - resourceCreate: createToken, - resourceDelete: deleteToken, + resource: token, + resourceCreate: createToken, + resourceCleanup: deleteToken, }) }) t.Run("EditCommand", func(t *testing.T) { s.testEditCommand(t, ctx, editCommandTestCase{ - resourceRef: getResourceRef(token), - resourceCreate: createToken, - resourceGet: getToken, - resourceDelete: deleteToken, + resourceRef: getResourceRef(token), + resourceCreate: createToken, + resourceGet: getToken, + resourceCleanup: deleteToken, }) }) } @@ -408,18 +412,18 @@ func (s *adminActionTestSuite) testOIDCConnector(t *testing.T) { t.Run("ResourceCommands", func(t *testing.T) { s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: connector, - resourceCreate: createOIDCConnector, - resourceDelete: deleteOIDCConnector, + resource: connector, + resourceCreate: createOIDCConnector, + resourceCleanup: deleteOIDCConnector, }) }) t.Run("EditCommand", func(t *testing.T) { s.testEditCommand(t, ctx, editCommandTestCase{ - resourceRef: getResourceRef(connector), - resourceCreate: createOIDCConnector, - resourceGet: getOIDCConnector, - resourceDelete: deleteOIDCConnector, + resourceRef: getResourceRef(connector), + resourceCreate: createOIDCConnector, + resourceGet: getOIDCConnector, + resourceCleanup: deleteOIDCConnector, }) }) } @@ -453,18 +457,18 @@ func (s *adminActionTestSuite) testSAMLConnector(t *testing.T) { t.Run("ResourceCommands", func(t *testing.T) { s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: connector, - resourceCreate: createSAMLConnector, - resourceDelete: deleteSAMLConnector, + resource: connector, + resourceCreate: createSAMLConnector, + resourceCleanup: deleteSAMLConnector, }) }) t.Run("EditCommand", func(t *testing.T) { s.testEditCommand(t, ctx, editCommandTestCase{ - resourceRef: getResourceRef(connector), - resourceCreate: createSAMLConnector, - resourceGet: getSAMLConnector, - resourceDelete: deleteSAMLConnector, + resourceRef: getResourceRef(connector), + resourceCreate: createSAMLConnector, + resourceGet: getSAMLConnector, + resourceCleanup: deleteSAMLConnector, }) }) } @@ -502,18 +506,18 @@ func (s *adminActionTestSuite) testGithubConnector(t *testing.T) { t.Run("ResourceCommands", func(t *testing.T) { s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: connector, - resourceCreate: createGithubConnector, - resourceDelete: deleteGithubConnector, + resource: connector, + resourceCreate: createGithubConnector, + resourceCleanup: deleteGithubConnector, }) }) t.Run("EditCommand", func(t *testing.T) { s.testEditCommand(t, ctx, editCommandTestCase{ - resourceRef: getResourceRef(connector), - resourceCreate: createGithubConnector, - resourceGet: getGithubConnector, - resourceDelete: deleteGithubConnector, + resourceRef: getResourceRef(connector), + resourceCreate: createGithubConnector, + resourceGet: getGithubConnector, + resourceCleanup: deleteGithubConnector, }) }) } @@ -551,26 +555,176 @@ func (s *adminActionTestSuite) testSAMLIdpServiceProvider(t *testing.T) { t.Run("ResourceCommands", func(t *testing.T) { s.testResourceCommand(t, ctx, resourceCommandTestCase{ - resource: sp, - resourceCreate: CreateSAMLIdPServiceProvider, - resourceDelete: deleteSAMLIdPServiceProvider, + resource: sp, + resourceCreate: CreateSAMLIdPServiceProvider, + resourceCleanup: deleteSAMLIdPServiceProvider, }) }) t.Run("EditCommand", func(t *testing.T) { s.testEditCommand(t, ctx, editCommandTestCase{ - resourceRef: getResourceRef(sp), - resourceCreate: CreateSAMLIdPServiceProvider, - resourceGet: getSAMLIdPServiceProvider, - resourceDelete: deleteSAMLIdPServiceProvider, + resourceRef: getResourceRef(sp), + resourceCreate: CreateSAMLIdPServiceProvider, + resourceGet: getSAMLIdPServiceProvider, + resourceCleanup: deleteSAMLIdPServiceProvider, + }) + }) +} + +func (s *adminActionTestSuite) testClusterAuthPreference(t *testing.T) { + ctx := context.Background() + + originalAuthPref, err := s.authServer.GetAuthPreference(ctx) + require.NoError(t, err) + + createAuthPref := func() error { + // To maintain the current auth preference for each test case, get + // the current auth preference from config and change it to dynamic. + authPref, err := s.authServer.GetAuthPreference(ctx) + if err != nil { + return trace.Wrap(err) + } + authPref.SetOrigin(types.OriginDynamic) + return s.authServer.SetAuthPreference(ctx, authPref) + } + + getAuthPref := func() (types.Resource, error) { + return s.authServer.GetAuthPreference(ctx) + } + + resetAuthPref := func() error { + return s.authServer.SetAuthPreference(ctx, originalAuthPref) + } + + t.Run("ResourceCommands", func(t *testing.T) { + s.testResourceCommand(t, ctx, resourceCommandTestCase{ + resource: originalAuthPref, + resourceCreate: createAuthPref, + resourceCleanup: resetAuthPref, + }) + }) + + t.Run("EditCommand", func(t *testing.T) { + s.testEditCommand(t, ctx, editCommandTestCase{ + resourceRef: getResourceRef(originalAuthPref), + resourceCreate: createAuthPref, + resourceGet: getAuthPref, + resourceCleanup: resetAuthPref, + }) + }) +} + +func (s *adminActionTestSuite) testNetworkRestriction(t *testing.T) { + ctx := context.Background() + + netRestrictions := types.NewNetworkRestrictions() + + createNetworkRestrictions := func() error { + return s.authServer.SetNetworkRestrictions(ctx, netRestrictions) + } + + getNetworkRestrictions := func() (types.Resource, error) { + return s.authServer.GetNetworkRestrictions(ctx) + } + + resetNetworkRestrictions := func() error { + return s.authServer.DeleteNetworkRestrictions(ctx) + } + + t.Run("ResourceCommands", func(t *testing.T) { + s.testResourceCommand(t, ctx, resourceCommandTestCase{ + resource: netRestrictions, + resourceCreate: createNetworkRestrictions, + resourceCleanup: resetNetworkRestrictions, + }) + }) + + t.Run("EditCommand", func(t *testing.T) { + s.testEditCommand(t, ctx, editCommandTestCase{ + resourceRef: getResourceRef(netRestrictions), + resourceCreate: createNetworkRestrictions, + resourceGet: getNetworkRestrictions, + resourceCleanup: resetNetworkRestrictions, + }) + }) +} + +func (s *adminActionTestSuite) testNetworkingConfig(t *testing.T) { + ctx := context.Background() + + netConfig := types.DefaultClusterNetworkingConfig() + netConfig.SetOrigin(types.OriginDynamic) + + createNetConfig := func() error { + return s.authServer.SetClusterNetworkingConfig(ctx, netConfig) + } + + getNetConfig := func() (types.Resource, error) { + return s.authServer.GetClusterNetworkingConfig(ctx) + } + + resetNetConfig := func() error { + return s.authServer.SetClusterNetworkingConfig(ctx, types.DefaultClusterNetworkingConfig()) + } + + t.Run("ResourceCommands", func(t *testing.T) { + s.testResourceCommand(t, ctx, resourceCommandTestCase{ + resource: netConfig, + resourceCreate: createNetConfig, + resourceCleanup: resetNetConfig, + }) + }) + + t.Run("EditCommand", func(t *testing.T) { + s.testEditCommand(t, ctx, editCommandTestCase{ + resourceRef: getResourceRef(netConfig), + resourceCreate: createNetConfig, + resourceGet: getNetConfig, + resourceCleanup: resetNetConfig, + }) + }) +} + +func (s *adminActionTestSuite) testSessionRecordingConfig(t *testing.T) { + ctx := context.Background() + + sessionRecordingConfig := types.DefaultSessionRecordingConfig() + sessionRecordingConfig.SetOrigin(types.OriginDynamic) + + createSessionRecordingConfig := func() error { + return s.authServer.SetSessionRecordingConfig(ctx, sessionRecordingConfig) + } + + getSessionRecordingConfig := func() (types.Resource, error) { + return s.authServer.GetSessionRecordingConfig(ctx) + } + + resetSessionRecordingConfig := func() error { + return s.authServer.SetSessionRecordingConfig(ctx, types.DefaultSessionRecordingConfig()) + } + + t.Run("ResourceCommands", func(t *testing.T) { + s.testResourceCommand(t, ctx, resourceCommandTestCase{ + resource: sessionRecordingConfig, + resourceCreate: createSessionRecordingConfig, + resourceCleanup: resetSessionRecordingConfig, + }) + }) + + t.Run("EditCommand", func(t *testing.T) { + s.testEditCommand(t, ctx, editCommandTestCase{ + resourceRef: getResourceRef(sessionRecordingConfig), + resourceCreate: createSessionRecordingConfig, + resourceGet: getSessionRecordingConfig, + resourceCleanup: resetSessionRecordingConfig, }) }) } type resourceCommandTestCase struct { - resource types.Resource - resourceCreate func() error - resourceDelete func() error + resource types.Resource + resourceCreate func() error + resourceCleanup func() error } func (s *adminActionTestSuite) testResourceCommand(t *testing.T, ctx context.Context, tc resourceCommandTestCase) { @@ -584,7 +738,7 @@ func (s *adminActionTestSuite) testResourceCommand(t *testing.T, ctx context.Con s.testCommand(t, ctx, adminActionTestCase{ command: fmt.Sprintf("create %v", f.Name()), cliCommand: &tctl.ResourceCommand{}, - cleanup: tc.resourceDelete, + cleanup: tc.resourceCleanup, }) }) @@ -593,7 +747,7 @@ func (s *adminActionTestSuite) testResourceCommand(t *testing.T, ctx context.Con command: fmt.Sprintf("create -f %v", f.Name()), cliCommand: &tctl.ResourceCommand{}, setup: tc.resourceCreate, - cleanup: tc.resourceDelete, + cleanup: tc.resourceCleanup, }) }) @@ -602,16 +756,16 @@ func (s *adminActionTestSuite) testResourceCommand(t *testing.T, ctx context.Con command: fmt.Sprintf("rm %v", getResourceRef(tc.resource)), cliCommand: &tctl.ResourceCommand{}, setup: tc.resourceCreate, - cleanup: tc.resourceDelete, + cleanup: tc.resourceCleanup, }) }) } type editCommandTestCase struct { - resourceRef string - resourceCreate func() error - resourceGet func() (types.Resource, error) - resourceDelete func() error + resourceRef string + resourceCreate func() error + resourceGet func() (types.Resource, error) + resourceCleanup func() error } func (s *adminActionTestSuite) testEditCommand(t *testing.T, ctx context.Context, tc editCommandTestCase) { @@ -634,7 +788,7 @@ func (s *adminActionTestSuite) testEditCommand(t *testing.T, ctx context.Context return nil }, }, - cleanup: tc.resourceDelete, + cleanup: tc.resourceCleanup, }) }) } @@ -844,8 +998,8 @@ func runTestCase(t *testing.T, ctx context.Context, client auth.ClientI, tc admi func getResourceRef(r types.Resource) string { switch kind := r.GetKind(); kind { - case types.KindClusterAuthPreference: - // single resources are referred to by kind alone. + case types.KindClusterAuthPreference, types.KindNetworkRestrictions, types.KindClusterNetworkingConfig, types.KindSessionRecordingConfig: + // singleton resources are referred to by kind alone. return kind default: return fmt.Sprintf("%v/%v", r.GetKind(), r.GetName()) diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index b2da724ede687..ae87ba57e38ac 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -1184,6 +1184,7 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client auth.ClientI) (err types.KindSessionRecordingConfig, types.KindInstaller, types.KindUIConfig, + types.KindNetworkRestrictions, } if !slices.Contains(singletonResources, rc.ref.Kind) && (rc.ref.Kind == "" || rc.ref.Name == "") { return trace.BadParameter("provide a full resource name to delete, for example:\n$ tctl rm cluster/east\n")