emit audit events when SFTP or SCP commands are blocked (#36977)

* emit audit events when SFTP or SCP commands are blocked

* include user name in SCP web audit message

* use 'an' instead of 'a'

* make SCP audit message use consistent language, fix type check error

* fix unused var
This commit is contained in:
Andrew LeFevre
2024-01-27 00:10:02 +00:00
committed by GitHub
parent 83033a68f3
commit 79583f754b
8 changed files with 111 additions and 2 deletions
+3
View File
@@ -290,6 +290,8 @@ const (
// Note: some requests (like exec into a pod) use other codes (like
// ExecCode).
KubeRequestCode = "T3009I"
// SCPDisallowedCode is the SCP disallowed event code.
SCPDisallowedCode = "T3010E"
// KubernetesClusterCreateCode is the kube.create event code.
KubernetesClusterCreateCode = "T3010I"
@@ -337,6 +339,7 @@ const (
SFTPSymlinkFailureCode = "TS018E"
SFTPLinkCode = "TS019I"
SFTPLinkFailureCode = "TS019E"
SFTPDisallowedCode = "TS020E"
// SessionCommandCode is a session command code.
SessionCommandCode = "T4000I"
+20
View File
@@ -261,6 +261,16 @@ func (e *localExec) String() string {
func (e *localExec) transformSecureCopy() error {
isSCPCmd, err := checkSCPAllowed(e.Ctx, e.GetCommand())
if err != nil {
e.Ctx.GetServer().EmitAuditEvent(context.WithoutCancel(e.Ctx.Context), &apievents.SFTP{
Metadata: apievents.Metadata{
Code: events.SCPDisallowedCode,
Type: events.SCPEvent,
Time: time.Now(),
},
UserMetadata: e.Ctx.Identity.GetUserMetadata(),
ServerMetadata: e.Ctx.GetServerMetadata(),
Error: err.Error(),
})
return trace.Wrap(err)
}
if !isSCPCmd {
@@ -356,6 +366,16 @@ func (e *remoteExec) SetCommand(command string) {
// ExecResult is only used to communicate an error while launching.
func (e *remoteExec) Start(ctx context.Context, ch ssh.Channel) (*ExecResult, error) {
if _, err := checkSCPAllowed(e.ctx, e.GetCommand()); err != nil {
e.ctx.GetServer().EmitAuditEvent(context.WithoutCancel(ctx), &apievents.SFTP{
Metadata: apievents.Metadata{
Code: events.SCPDisallowedCode,
Type: events.SCPEvent,
Time: time.Now(),
},
UserMetadata: e.ctx.Identity.GetUserMetadata(),
ServerMetadata: e.ctx.GetServerMetadata(),
Error: err.Error(),
})
return nil, trace.Wrap(err)
}
+13 -1
View File
@@ -27,6 +27,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/google/uuid"
"github.com/gravitational/trace"
@@ -1361,7 +1362,18 @@ func (s *Server) handleSubsystem(ctx context.Context, ch ssh.Channel, req *ssh.R
// if SFTP was requested, check that
if subsystem.subsytemName == teleport.SFTPSubsystem {
if err := serverContext.CheckSFTPAllowed(s.sessionRegistry); err != nil {
err := serverContext.CheckSFTPAllowed(s.sessionRegistry)
if err != nil {
s.EmitAuditEvent(context.WithoutCancel(ctx), &apievents.SFTP{
Metadata: apievents.Metadata{
Code: events.SFTPDisallowedCode,
Type: events.SFTPEvent,
Time: time.Now(),
},
UserMetadata: serverContext.Identity.GetUserMetadata(),
ServerMetadata: serverContext.GetServerMetadata(),
Error: err.Error(),
})
return trace.Wrap(err)
}
}
+12
View File
@@ -25,6 +25,7 @@ import (
"io"
"os"
"os/exec"
"time"
"github.com/gogo/protobuf/jsonpb"
"github.com/gravitational/trace"
@@ -33,6 +34,7 @@ import (
"github.com/gravitational/teleport"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/utils"
)
@@ -66,6 +68,16 @@ func (s *sftpSubsys) Start(ctx context.Context,
// this connection was proxied, the proxy doesn't know if file copying
// is allowed for certain Nodes.
if !serverCtx.AllowFileCopying {
serverCtx.GetServer().EmitAuditEvent(context.WithoutCancel(ctx), &apievents.SFTP{
Metadata: apievents.Metadata{
Code: events.SFTPDisallowedCode,
Type: events.SFTPEvent,
Time: time.Now(),
},
UserMetadata: serverCtx.Identity.GetUserMetadata(),
ServerMetadata: serverCtx.GetServerMetadata(),
Error: srv.ErrNodeFileCopyingNotPermitted.Error(),
})
return srv.ErrNodeFileCopyingNotPermitted
}
+12 -1
View File
@@ -2154,7 +2154,18 @@ func (s *Server) parseSubsystemRequest(req *ssh.Request, ctx *srv.ServerContext)
case r.Name == teleport.GetHomeDirSubsystem:
return newHomeDirSubsys(), nil
case r.Name == teleport.SFTPSubsystem:
if err := ctx.CheckSFTPAllowed(s.reg); err != nil {
err := ctx.CheckSFTPAllowed(s.reg)
if err != nil {
s.EmitAuditEvent(context.Background(), &apievents.SFTP{
Metadata: apievents.Metadata{
Code: events.SFTPDisallowedCode,
Type: events.SFTPEvent,
Time: time.Now(),
},
UserMetadata: ctx.Identity.GetUserMetadata(),
ServerMetadata: ctx.GetServerMetadata(),
Error: err.Error(),
})
return nil, trace.Wrap(err)
}
@@ -47,6 +47,7 @@ const EventIconMap: Record<EventCode, any> = {
[eventCodes.SCP_DOWNLOAD]: Icons.Download,
[eventCodes.SCP_UPLOAD_FAILURE]: Icons.Upload,
[eventCodes.SCP_UPLOAD]: Icons.Upload,
[eventCodes.SCP_DISALLOWED]: Icons.FolderPlus,
[eventCodes.SFTP_OPEN_FAILURE]: Icons.FolderPlus,
[eventCodes.SFTP_OPEN]: Icons.FolderPlus,
[eventCodes.SFTP_CLOSE_FAILURE]: Icons.FolderPlus,
@@ -83,6 +84,9 @@ const EventIconMap: Record<EventCode, any> = {
[eventCodes.SFTP_READLINK]: Icons.FolderPlus,
[eventCodes.SFTP_SYMLINK_FAILURE]: Icons.FolderPlus,
[eventCodes.SFTP_SYMLINK]: Icons.FolderPlus,
[eventCodes.SFTP_LINK]: Icons.FolderPlus,
[eventCodes.SFTP_LINK_FAILURE]: Icons.FolderPlus,
[eventCodes.SFTP_DISALLOWED]: Icons.FolderPlus,
[eventCodes.APP_SESSION_CHUNK]: Icons.Info,
[eventCodes.APP_SESSION_START]: Icons.Info,
[eventCodes.APP_SESSION_END]: Icons.Info,
@@ -281,6 +281,14 @@ export const formatters: Formatters = {
rest['server_hostname'] || rest['addr.local']
}] failed [${exitError}]`,
},
[eventCodes.SCP_DISALLOWED]: {
type: 'scp',
desc: 'SCP Disallowed',
format: ({ user, ...rest }) =>
`User [${user}] SCP file transfer on node [${
rest['server_hostname'] || rest['addr.local']
}] blocked`,
},
[eventCodes.SFTP_OPEN]: {
type: 'sftp',
desc: 'SFTP Open',
@@ -569,6 +577,30 @@ export const formatters: Formatters = {
rest['server_hostname'] || rest['addr.local']
}]: [${error}]`,
},
[eventCodes.SFTP_LINK]: {
type: 'sftp',
desc: 'SFTP Link',
format: ({ user, path, ...rest }) =>
`User [${user}] created hard link [${path}] on node [${
rest['server_hostname'] || rest['addr.local']
}]`,
},
[eventCodes.SFTP_LINK_FAILURE]: {
type: 'sftp',
desc: 'SFTP Link Failed',
format: ({ user, path, error, ...rest }) =>
`User [${user}] failed to create hard link [${path}] on node [${
rest['server_hostname'] || rest['addr.local']
}]: [${error}]`,
},
[eventCodes.SFTP_DISALLOWED]: {
type: 'sftp',
desc: 'SFTP Disallowed',
format: ({ user, ...rest }) =>
`User [${user}] was blocked from creating an SFTP session on node [${
rest['server_hostname'] || rest['addr.local']
}]`,
},
[eventCodes.SESSION_JOIN]: {
type: 'session.join',
desc: 'User Joined',
@@ -152,6 +152,7 @@ export const eventCodes = {
SCP_DOWNLOAD: 'T3004I',
SCP_UPLOAD_FAILURE: 'T3005E',
SCP_UPLOAD: 'T3005I',
SCP_DISALLOWED: 'T3010E',
SFTP_OPEN_FAILURE: 'TS001E',
SFTP_OPEN: 'TS001I',
SFTP_CLOSE_FAILURE: 'TS002E',
@@ -188,6 +189,9 @@ export const eventCodes = {
SFTP_READLINK: 'TS017I',
SFTP_SYMLINK_FAILURE: 'TS018E',
SFTP_SYMLINK: 'TS018I',
SFTP_LINK: 'TS019I',
SFTP_LINK_FAILURE: 'TS019E',
SFTP_DISALLOWED: 'TS020E',
SESSION_COMMAND: 'T4000I',
SESSION_DATA: 'T2006I',
SESSION_DISK: 'T4001I',
@@ -405,6 +409,12 @@ export type RawEvents = {
exitError: string;
}
>;
[eventCodes.SCP_DISALLOWED]: RawEvent<
typeof eventCodes.SCP_DISALLOWED,
{
user: string;
}
>;
[eventCodes.SFTP_OPEN]: RawEventSFTP<typeof eventCodes.SFTP_OPEN>;
[eventCodes.SFTP_OPEN_FAILURE]: RawEventSFTP<
typeof eventCodes.SFTP_OPEN_FAILURE
@@ -477,6 +487,11 @@ export type RawEvents = {
[eventCodes.SFTP_SYMLINK_FAILURE]: RawEventSFTP<
typeof eventCodes.SFTP_SYMLINK_FAILURE
>;
[eventCodes.SFTP_LINK]: RawEventSFTP<typeof eventCodes.SFTP_LINK>;
[eventCodes.SFTP_LINK_FAILURE]: RawEventSFTP<
typeof eventCodes.SFTP_LINK_FAILURE
>;
[eventCodes.SFTP_DISALLOWED]: RawEventSFTP<typeof eventCodes.SFTP_DISALLOWED>;
[eventCodes.SESSION_COMMAND]: RawEventCommand<
typeof eventCodes.SESSION_COMMAND
>;