-
Notifications
You must be signed in to change notification settings - Fork 899
feat: block file transfers for security #13501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
94a4067
9915cde
abd97df
00e0a8a
6cec719
fa0010d
dbaa763
94926bd
d92c070
47ee9b0
4e45761
d852417
96ca9ae
19fe5cf
e3a9dba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,11 @@ const ( | |
// MagicProcessCmdlineJetBrains is a string in a process's command line that | ||
// uniquely identifies it as JetBrains software. | ||
MagicProcessCmdlineJetBrains = "idea.vendor.name=JetBrains" | ||
|
||
// BlockedFileTransferErrorCode indicates that SSH server restricted the raw command from performing | ||
// the file transfer. | ||
BlockedFileTransferErrorCode = 2 | ||
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
BlockedFileTransferErrorMessage = "File transfer has been disabled." | ||
) | ||
|
||
// Config sets configuration parameters for the agent SSH server. | ||
|
@@ -74,6 +79,8 @@ type Config struct { | |
// X11SocketDir is the directory where X11 sockets are created. Default is | ||
// /tmp/.X11-unix. | ||
X11SocketDir string | ||
// BlockFileTransfer restricts use of file transfer applications. | ||
BlockFileTransfer bool | ||
} | ||
|
||
type Server struct { | ||
|
@@ -272,6 +279,18 @@ func (s *Server) sessionHandler(session ssh.Session) { | |
extraEnv = append(extraEnv, fmt.Sprintf("DISPLAY=:%d.0", x11.ScreenNumber)) | ||
} | ||
|
||
if s.fileTransferBlocked(session) { | ||
s.logger.Warn(ctx, "file transfer blocked", slog.F("session_subsystem", session.Subsystem()), slog.F("raw_command", session.RawCommand())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. out of scope: we may want to eventually have a way to surface this to admins as they may be interested in this sort of thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a new metric? maybe consider this as a follow-up option. I don't know if an admin wants to review notifications from users accidentally trying to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we run the risk here of logging a potentially large amount of text here? Aside: this feels like a good use-case for an audit-log. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as #13501 (comment)? Yeah, the problem here is that it is logged on the agent side in |
||
|
||
if session.Subsystem() == "" { // sftp does not expect error, otherwise it fails with "package too long" | ||
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Response format: <status_code><message body>\n | ||
errorMessage := fmt.Sprintf("\x02%s\n", BlockedFileTransferErrorMessage) | ||
_, _ = session.Write([]byte(errorMessage)) | ||
} | ||
_ = session.Exit(BlockedFileTransferErrorCode) | ||
return | ||
} | ||
|
||
switch ss := session.Subsystem(); ss { | ||
case "": | ||
case "sftp": | ||
|
@@ -322,6 +341,35 @@ func (s *Server) sessionHandler(session ssh.Session) { | |
_ = session.Exit(0) | ||
} | ||
|
||
// fileTransferBlocked method checks if the file transfer commands should be blocked. | ||
// Warning: consider this mechanism as "Do not trespass" sign. If a user needs a more sophisticated | ||
// and battle-proof solution, consider the full endpoint security. | ||
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
func (s *Server) fileTransferBlocked(session ssh.Session) bool { | ||
if !s.config.BlockFileTransfer { | ||
return false // file transfers are permitted | ||
} | ||
// File transfers are restricted. | ||
|
||
if session.Subsystem() == "sftp" { | ||
return true | ||
} | ||
|
||
cmd := session.Command() | ||
if len(cmd) == 0 { | ||
return false // no command? | ||
} | ||
|
||
c := cmd[0] | ||
c = filepath.Base(c) // in case the binary is absolute path, /usr/sbin/scp | ||
|
||
switch c { | ||
case "nc", "rsync", "scp", "sftp": | ||
return true // forbidden command | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
func (s *Server) sessionStart(logger slog.Logger, session ssh.Session, extraEnv []string) (retErr error) { | ||
ctx := session.Context() | ||
env := append(session.Environ(), extraEnv...) | ||
|
Uh oh!
There was an error while loading. Please reload this page.