Skip to content

Commit

Permalink
Add SessionRecordingConfig support to cluster configuration RPC servi…
Browse files Browse the repository at this point in the history
…ce (#38591)

* Remove marshal options from GetSessionRecordingConfig

* Add SessionRecordingConfig support to cluster configuration RPC service

Extends the clusterconfigv1.Service to implement RPCs for interacting
with SessionRecordingConfig. The various GetSessionRecordingConfig
interfaces were also all updated to remove the marshal options since
they were never used and served no purpose.
  • Loading branch information
rosstimothy committed Mar 15, 2024
1 parent 9fa4c1c commit 6b89d46
Show file tree
Hide file tree
Showing 12 changed files with 570 additions and 23 deletions.
18 changes: 9 additions & 9 deletions lib/auth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ type ReadNodeAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetRole returns role by name
GetRole(ctx context.Context, name string) (types.Role, error)
Expand Down Expand Up @@ -182,7 +182,7 @@ type ReadProxyAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetUIConfig returns configuration for the UI served by the proxy service
GetUIConfig(ctx context.Context) (types.UIConfig, error)
Expand Down Expand Up @@ -339,7 +339,7 @@ type ReadRemoteProxyAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetRole returns role by name
GetRole(ctx context.Context, name string) (types.Role, error)
Expand Down Expand Up @@ -430,7 +430,7 @@ type ReadKubernetesAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetUser returns a services.User for this cluster.
GetUser(ctx context.Context, name string, withSecrets bool) (types.User, error)
Expand Down Expand Up @@ -496,7 +496,7 @@ type ReadAppsAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetUser returns a services.User for this cluster.
GetUser(ctx context.Context, name string, withSecrets bool) (types.User, error)
Expand Down Expand Up @@ -563,7 +563,7 @@ type ReadDatabaseAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetUser returns a services.User for this cluster.
GetUser(ctx context.Context, name string, withSecrets bool) (types.User, error)
Expand Down Expand Up @@ -630,7 +630,7 @@ type ReadWindowsDesktopAccessPoint interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetUser returns a services.User for this cluster.
GetUser(ctx context.Context, name string, withSecrets bool) (types.User, error)
Expand Down Expand Up @@ -896,7 +896,7 @@ type AccessCache interface {
GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNetworkingConfig, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetClusterName gets the name of the cluster from the backend.
GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error)
Expand Down Expand Up @@ -927,7 +927,7 @@ type Cache interface {
GetAuthPreference(ctx context.Context) (types.AuthPreference, error)

// GetSessionRecordingConfig returns session recording configuration.
GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)

// GetNamespaces returns a list of namespaces
GetNamespaces() ([]types.Namespace, error)
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -4465,13 +4465,13 @@ func (a *ServerWithRoles) ResetClusterNetworkingConfig(ctx context.Context) erro
}

// GetSessionRecordingConfig gets session recording configuration.
func (a *ServerWithRoles) GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error) {
func (a *ServerWithRoles) GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error) {
if err := a.action(apidefaults.Namespace, types.KindSessionRecordingConfig, types.VerbRead); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, types.VerbRead); err2 != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetSessionRecordingConfig(ctx, opts...)
return a.authServer.GetSessionRecordingConfig(ctx)
}

// SetSessionRecordingConfig sets session recording configuration.
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ func (c *Client) GetClusterNetworkingConfig(ctx context.Context) (types.ClusterN
}

// GetSessionRecordingConfig gets session recording configuration.
func (c *Client) GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error) {
func (c *Client) GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error) {
return c.APIClient.GetSessionRecordingConfig(ctx)
}

Expand Down
178 changes: 178 additions & 0 deletions lib/auth/clusterconfig/clusterconfigv1/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
type Cache interface {
GetAuthPreference(context.Context) (types.AuthPreference, error)
GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNetworkingConfig, error)
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)
}

// Backend is used by the [Service] to mutate cluster config resources.
Expand All @@ -43,6 +44,10 @@ type Backend interface {
CreateClusterNetworkingConfig(ctx context.Context, preference types.ClusterNetworkingConfig) (types.ClusterNetworkingConfig, error)
UpdateClusterNetworkingConfig(ctx context.Context, preference types.ClusterNetworkingConfig) (types.ClusterNetworkingConfig, error)
UpsertClusterNetworkingConfig(ctx context.Context, preference types.ClusterNetworkingConfig) (types.ClusterNetworkingConfig, error)

CreateSessionRecordingConfig(ctx context.Context, preference types.SessionRecordingConfig) (types.SessionRecordingConfig, error)
UpdateSessionRecordingConfig(ctx context.Context, preference types.SessionRecordingConfig) (types.SessionRecordingConfig, error)
UpsertSessionRecordingConfig(ctx context.Context, preference types.SessionRecordingConfig) (types.SessionRecordingConfig, error)
}

// ServiceConfig contain dependencies required to create a [Service].
Expand Down Expand Up @@ -350,6 +355,8 @@ func (s *Service) UpdateClusterNetworkingConfig(ctx context.Context, req *cluste
return nil, trace.AccessDenied("proxy peering is an enterprise-only feature")
}

req.ClusterNetworkConfig.SetOrigin(types.OriginDynamic)

updated, err := s.backend.UpdateClusterNetworkingConfig(ctx, req.ClusterNetworkConfig)
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -389,6 +396,8 @@ func (s *Service) UpsertClusterNetworkingConfig(ctx context.Context, req *cluste
return nil, trace.AccessDenied("proxy peering is an enterprise-only feature")
}

req.ClusterNetworkConfig.SetOrigin(types.OriginDynamic)

upserted, err := s.backend.UpsertClusterNetworkingConfig(ctx, req.GetClusterNetworkConfig())
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -453,3 +462,172 @@ func (s *Service) ResetClusterNetworkingConfig(ctx context.Context, _ *clusterco

return nil, trace.LimitExceeded("failed to reset networking config within %v iterations", iterationLimit)
}

// GetSessionRecordingConfig returns the locally cached networking configuration.
func (s *Service) GetSessionRecordingConfig(ctx context.Context, _ *clusterconfigpb.GetSessionRecordingConfigRequest) (*types.SessionRecordingConfigV2, error) {
authzCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if err := authzCtx.CheckAccessToKind(types.KindSessionRecordingConfig, types.VerbRead); err != nil {
if err2 := authzCtx.CheckAccessToKind(types.KindClusterConfig, types.VerbRead); err2 != nil {
return nil, trace.Wrap(err)
}
}

netConfig, err := s.cache.GetSessionRecordingConfig(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

cfgV2, ok := netConfig.(*types.SessionRecordingConfigV2)
if !ok {
return nil, trace.Wrap(trace.BadParameter("unexpected session recording config type %T (expected %T)", netConfig, cfgV2))
}
return cfgV2, nil
}

// CreateSessionRecordingConfig creates a new cluster networking configuration if one does not exist.
// This is an internal API and is not exposed via [clusterconfigv1.ClusterConfigServiceServer]. It
// is only meant to be called directly from the first time an Auth instance is started.
func (s *Service) CreateSessionRecordingConfig(ctx context.Context, cfg types.SessionRecordingConfig) (types.SessionRecordingConfig, error) {
authzCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if !authz.HasBuiltinRole(*authzCtx, string(types.RoleAuth)) {
return nil, trace.AccessDenied("this request can be only executed by an auth server")
}

created, err := s.backend.CreateSessionRecordingConfig(ctx, cfg)
if err != nil {
return nil, trace.Wrap(err)
}

cfgV2, ok := created.(*types.SessionRecordingConfigV2)
if !ok {
return nil, trace.Wrap(trace.BadParameter("unexpected session recording config type %T (expected %T)", created, cfgV2))
}

return cfgV2, nil
}

// UpdateSessionRecordingConfig conditionally updates an existing networking configuration if the
// value wasn't concurrently modified.
func (s *Service) UpdateSessionRecordingConfig(ctx context.Context, req *clusterconfigpb.UpdateSessionRecordingConfigRequest) (*types.SessionRecordingConfigV2, error) {
authzCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if err := authzCtx.CheckAccessToKind(types.KindSessionRecordingConfig, types.VerbUpdate); err != nil {
if err2 := authzCtx.CheckAccessToKind(types.KindClusterConfig, types.VerbUpdate); err2 != nil {
return nil, trace.Wrap(err)
}
}

// Support reused MFA for bulk tctl create requests.
if err := authzCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil {
return nil, trace.Wrap(err)
}

req.SessionRecordingConfig.SetOrigin(types.OriginDynamic)

updated, err := s.backend.UpdateSessionRecordingConfig(ctx, req.SessionRecordingConfig)
if err != nil {
return nil, trace.Wrap(err)
}

cfgV2, ok := updated.(*types.SessionRecordingConfigV2)
if !ok {
return nil, trace.Wrap(trace.BadParameter("unexpected session recording config type %T (expected %T)", updated, cfgV2))
}

return cfgV2, nil
}

// UpsertSessionRecordingConfig creates a new networking configuration or overwrites an existing configuration.
func (s *Service) UpsertSessionRecordingConfig(ctx context.Context, req *clusterconfigpb.UpsertSessionRecordingConfigRequest) (*types.SessionRecordingConfigV2, error) {
authzCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if err := authzCtx.CheckAccessToKind(types.KindSessionRecordingConfig, types.VerbCreate, types.VerbUpdate); err != nil {
if err2 := authzCtx.CheckAccessToKind(types.KindClusterConfig, types.VerbCreate, types.VerbUpdate); err2 != nil {
return nil, trace.Wrap(err)
}
}

// Support reused MFA for bulk tctl create requests.
if err := authzCtx.AuthorizeAdminActionAllowReusedMFA(); err != nil {
return nil, trace.Wrap(err)
}

req.SessionRecordingConfig.SetOrigin(types.OriginDynamic)

upserted, err := s.backend.UpsertSessionRecordingConfig(ctx, req.SessionRecordingConfig)
if err != nil {
return nil, trace.Wrap(err)
}

cfgV2, ok := upserted.(*types.SessionRecordingConfigV2)
if !ok {
return nil, trace.Wrap(trace.BadParameter("unexpected session recording config type %T (expected %T)", upserted, cfgV2))
}

return cfgV2, nil
}

// ResetSessionRecordingConfig restores the networking configuration to the default value.
func (s *Service) ResetSessionRecordingConfig(ctx context.Context, _ *clusterconfigpb.ResetSessionRecordingConfigRequest) (*types.SessionRecordingConfigV2, error) {
authzCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if err := authzCtx.CheckAccessToKind(types.KindSessionRecordingConfig, types.VerbUpdate); err != nil {
if err2 := authzCtx.CheckAccessToKind(types.KindClusterConfig, types.VerbUpdate); err2 != nil {
return nil, trace.Wrap(err)
}
}

if err := authzCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}
defaultConfig := types.DefaultSessionRecordingConfig()
const iterationLimit = 3
// Attempt a few iterations in case the conditional update fails
// due to spurious networking conditions.
for i := 0; i < iterationLimit; i++ {

cfg, err := s.cache.GetSessionRecordingConfig(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if cfg.Origin() == types.OriginConfigFile {
return nil, trace.BadParameter("session recording configuration has been defined in the auth server's config file and cannot be set back to defaults dynamically.")
}

defaultConfig.SetRevision(cfg.GetRevision())

reset, err := s.backend.UpsertSessionRecordingConfig(ctx, defaultConfig)
if err != nil {
if trace.IsCompareFailed(err) {
continue
}
return nil, trace.Wrap(err)
}

cfgV2, ok := reset.(*types.SessionRecordingConfigV2)
if !ok {
return nil, trace.Wrap(trace.BadParameter("unexpected session recording config type %T (expected %T)", reset, cfgV2))
}

return cfgV2, nil
}
return nil, trace.LimitExceeded("failed to reset networking config within %v iterations", iterationLimit)
}
Loading

0 comments on commit 6b89d46

Please sign in to comment.