sshproxyplus

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2022 License: MIT Imports: 25 Imported by: 0

README

SSH Proxy+

Purpose

The goal of this project is to create an ssh proxy that can provide real-time logging, mirroring, and monitoring of ssh sessions as they occur.

What is it?

The primary components of this library are depicted below.

  • Proxy - the proxy has a ListenIP and ListenPort that it listens on for SSH clients. The client authenticates with a username and password; the Proxy matches that to a corresponding ProxyUser that is configured for the proxy. The client is then connected to the remote host for that ProxyUser. There is also an option to allow all authentication requests and to forward them to a default remote server.

  • ProxyUser - the ProxyUser has the Username and Password required to authenticate, the RemoteHost to connect to, the RemoteUsername and RemotePassword to use with the RemoteHost, and a list of EventCallbacks and channelFilters to use on an any events that occur in any sessions that occur.

    • EventCallbacks are non-blocking anonymous functions that can be called when an event occurs
    • EventFilters are blocking anonymous functions that process message events and return processed data.
  • SessionContext - a SessionContext is created when a new user authenticates to a proxy. It is used to track everything associated with a given session. Every event that occurs is stored in memory and is also written to disk in JSON.

  • ProxyController - This is both an API to create and manage proxies, as well as a socket interface for a remote client to manage the controller. A remote client is given a preshared key that can be used to send HMAC-signed messages to configure the proxy.

  • WebServer - the ProxyController WebServer is an http server that provides web browsers with the ability to view live and replays of sessions. There are two modes:

    • the WebServer can be configured to make all sessions visible;
    • alternatively, a client can provide a ProxyID and a Secret to connect to a proxySessionViewer that corresponds to a ProxyUser for a given proxy. These can be either tied to a single session, or to all sessions for that user.

Demo:

You can view a live proxy WebServer and Proxy running at https://proxy.jianmin.dev/

Dependencies

For xterm.js to work, you'll need to pull down the source files.

Go to html/js and run:

npm install --save xterm.js
npm install --save xterm-addon-fit

For me, on stock Ubuntu server 20.04, this places the xterm source in:

html/js/node_modules/xterm/

and

html/js/nod_modules/xterm-addon-fit/

Usage:

Launching Go Server
go mod tidy
go run .

For usage:

go run _example/sshproxyplus.go --help

Tests:

go test -cover
Launching Static HTML server (for replays)
cd html
python3 -m http.server

Supported Channel Types:

  • exec
  • tty

Unsupported Channel Types:

  • tunnels. not the goal of this project

References

Documentation

Overview

sshproxyplus is a tool to proxy SSH sessions while recording the behavior in real time.

The tool comes with a companion web-server that allows one to view a proxied session in real-time and to reply previous sessions.

There proxy is managed by a controller interface that can be configured directly via code, or over a socket via signed commands.

Index

Constants

View Source
const ACTIVE_POLLING_DELAY time.Duration = 500 * time.Millisecond
View Source
const CONTROLLER_MESSAGE_ACTIVATE_PROXY string = "activate-proxy"
View Source
const CONTROLLER_MESSAGE_ADD_CHANNEL_FILTER string = "add-channel-filter"
View Source
const CONTROLLER_MESSAGE_ADD_PROXY_USER string = "add-proxy-user"
View Source
const CONTROLLER_MESSAGE_ADD_USER_CALLBACK string = "add-user-callback"
View Source
const CONTROLLER_MESSAGE_CREATE_PROXY string = "create-proxy"
View Source
const CONTROLLER_MESSAGE_DEACTIVATE_PROXY string = "deactivate-proxy"
View Source
const CONTROLLER_MESSAGE_DESTROY_PROXY string = "destroy-proxy"
View Source
const CONTROLLER_MESSAGE_GET_PROXY_INFO string = "get-proxy-info"
View Source
const CONTROLLER_MESSAGE_GET_PROXY_VIEWER string = "get-proxy-viewer"
View Source
const CONTROLLER_MESSAGE_GET_PROXY_VIEWERS string = "get-proxy-viewers"
View Source
const CONTROLLER_MESSAGE_LIST_PROXIES string = "list-proxies"
View Source
const CONTROLLER_MESSAGE_NEW_PROXY_VIEWER string = "new-proxy-viewer"
View Source
const CONTROLLER_MESSAGE_REMOVE_CHANNEL_FILTER string = "remove-channel-filter"
View Source
const CONTROLLER_MESSAGE_REMOVE_PROXY_USER string = "remove-proxy-user"
View Source
const CONTROLLER_MESSAGE_REMOVE_USER_CALLBACK string = "remove-user-callback"
View Source
const CONTROLLER_MESSAGE_START_PROXY string = "start-proxy"
View Source
const CONTROLLER_MESSAGE_STOP_PROXY string = "stop-proxy"
View Source
const EVENT_MESSAGE string = "new-message"
View Source
const EVENT_NEW_CHANNEL string = "new-channel"
View Source
const EVENT_NEW_REQUEST string = "new-request"
View Source
const EVENT_SESSION_START string = "session-start"
View Source
const EVENT_SESSION_STOP string = "session-stop"
View Source
const EVENT_WINDOW_RESIZE string = "window-resize"
View Source
const PROXY_CONTROLLER_SOCKET_PLAIN uint16 = 0

A simple TCP socket with no encryption

View Source
const PROXY_CONTROLLER_SOCKET_PLAIN_WEBSOCKET uint16 = 1

A HTTP websocket with no encryption It can be used with web servers

View Source
const PROXY_CONTROLLER_SOCKET_TLS uint16 = 2

A simple socket wrapped in TLS

View Source
const PROXY_CONTROLLER_SOCKET_TLS_WEBSOCKET uint16 = 3

A HTTPS websocket that can be used with web servers

View Source
const SESSION_LIST_FN string = ".session_list"
View Source
const SESSION_VIEWER_EXPIRATION = -1
View Source
const SESSION_VIEWER_SECRET_LEN = 64
View Source
const SESSION_VIEWER_TYPE_LIST = 1
View Source
const SESSION_VIEWER_TYPE_SINGLE = 0
View Source
const SIGNAL_NEW_MESSAGE int = 1
View Source
const SIGNAL_SESSION_END int = 0

Variables

This section is empty.

Functions

Types

type ChannelFilterFunc

type ChannelFilterFunc struct {
	// contains filtered or unexported fields
}

type ControllerHMAC

type ControllerHMAC struct {
	Message []byte
	HMAC    []byte
}

A signed message for the controller. The Message is a JSON blob The HMAC is a signed hash of the Message

func (*ControllerHMAC) Verify

func (messageWrapper *ControllerHMAC) Verify(key []byte) (error, ControllerMessage)

type ControllerMessage

type ControllerMessage struct {
	MessageType   string
	ProxyData     []byte     `json:"ProxyData,omitempty"`
	ProxyID       uint64     `json:"omitempty"`
	ViewerSecret  string     `json:"omitempty"`
	SessionKey    string     `json:"omitempty"`
	Username      string     `json:"omitempty"`
	Password      string     `json:"omitempty"`
	FilterKey     string     `json:"omitempty"`
	CallbackKey   string     `json:"omitempty"`
	CallbackURL   string     `json:"omitempty"`
	ProxyUser     *ProxyUser `json:"omitempty"`
	FindString    []byte     `json:"omitempty"`
	ReplaceString []byte     `json:"omitempty"`
}

A message for the controller. If a field isn't used for a particular message type, it is omitted.

func (*ControllerMessage) HandleMessage

func (message *ControllerMessage) HandleMessage(controller *ProxyController) []byte

func (*ControllerMessage) Sign

func (message *ControllerMessage) Sign(key []byte) (error, ControllerHMAC)

type EventCallback

type EventCallback struct {
	// contains filtered or unexported fields
}

type EventCallbackFunc

type EventCallbackFunc func(SessionEvent)

type LoggerInterface

type LoggerInterface interface {
	Printf(format string, v ...any)
	Println(v ...any)
}

type ProxyContext

type ProxyContext struct {
	DefaultRemotePort int
	DefaultRemoteIP   string
	ListenIP          string
	ListenPort        int

	Log              LoggerInterface `json:"-"`
	SessionFolder    string
	TLSCert          string
	TLSKey           string
	OverridePassword string
	OverrideUser     string
	WebListenPort    int
	ServerVersion    string
	Users            map[string]*ProxyUser

	RequireValidPassword bool

	PublicAccess bool
	Viewers      map[string]*proxySessionViewer
	BaseURI      string
	// contains filtered or unexported fields
}

Typically the proxy should be managed via the ProxyController functions and not directly called.

func MakeNewProxy

func MakeNewProxy(signer ssh.Signer) *ProxyContext

func (*ProxyContext) Activate

func (proxy *ProxyContext) Activate()

func (*ProxyContext) AddProxyUser

func (proxy *ProxyContext) AddProxyUser(user *ProxyUser) string

func (*ProxyContext) AddSessionToSessionList

func (proxy *ProxyContext) AddSessionToSessionList(session *SessionContext)

func (*ProxyContext) AddSessionToUserList

func (proxy *ProxyContext) AddSessionToUserList(session *SessionContext)

func (*ProxyContext) AddSessionViewer

func (proxy *ProxyContext) AddSessionViewer(viewer *proxySessionViewer)

func (*ProxyContext) AuthenticateUser

func (proxy *ProxyContext) AuthenticateUser(username, password string) (error, *ProxyUser)

func (*ProxyContext) Deactivate

func (proxy *ProxyContext) Deactivate()

func (*ProxyContext) GetDefaultRemoteHost

func (proxy *ProxyContext) GetDefaultRemoteHost() string

func (*ProxyContext) GetProxyUser

func (proxy *ProxyContext) GetProxyUser(username, password string, cloneUser bool) (error, *ProxyUser, bool)

func (*ProxyContext) GetSessionViewer

func (proxy *ProxyContext) GetSessionViewer(key string) *proxySessionViewer

func (*ProxyContext) HandleClientConn

func (proxy *ProxyContext) HandleClientConn(client_conn *ssh.ServerConn, client_channels <-chan ssh.NewChannel, client_requests <-chan *ssh.Request, curSession *SessionContext)

func (*ProxyContext) Initialize

func (proxy *ProxyContext) Initialize(defaultSigner ssh.Signer)

func (*ProxyContext) IsActive

func (proxy *ProxyContext) IsActive() bool

func (*ProxyContext) ListAllActiveSessions

func (proxy *ProxyContext) ListAllActiveSessions() []string

func (*ProxyContext) ListAllActiveUserSessions

func (proxy *ProxyContext) ListAllActiveUserSessions(user string) []string

func (*ProxyContext) ListAllSessions

func (proxy *ProxyContext) ListAllSessions() []string

func (*ProxyContext) ListAllUserSessions

func (proxy *ProxyContext) ListAllUserSessions(user string) []string

func (*ProxyContext) MakeSessionViewerForSession

func (proxy *ProxyContext) MakeSessionViewerForSession(user_key string, password string, session string) (error, *proxySessionViewer)

func (*ProxyContext) MakeSessionViewerForUser

func (proxy *ProxyContext) MakeSessionViewerForUser(username, password string) (error, *proxySessionViewer)

func (*ProxyContext) RemoveExpiredSessions

func (proxy *ProxyContext) RemoveExpiredSessions()

func (*ProxyContext) RemoveProxyUser

func (proxy *ProxyContext) RemoveProxyUser(username string, password string) error

func (*ProxyContext) RemoveSessionViewer

func (proxy *ProxyContext) RemoveSessionViewer(key string)

func (*ProxyContext) StartProxy

func (proxy *ProxyContext) StartProxy()

func (*ProxyContext) Stop

func (proxy *ProxyContext) Stop()

type ProxyController

type ProxyController struct {
	Proxies      map[uint64]*ProxyContext
	ProxyCounter uint64
	// Used to authenticate commands
	// sent over the controller socket
	// from a remote server
	PresharedKey string
	SocketType   uint16
	SocketHost   string

	TLSKey  string
	TLSCert string

	WebHost       string
	WebStaticDir  string
	BaseURI       string
	Log           LoggerInterface `json:"-"`
	DefaultSigner ssh.Signer      `json:"-"`

	EventCallbacks map[string]*EventCallback
	// contains filtered or unexported fields
}

The ProxyController object can be managed directly via the software API, or over a controller socket.

It can be used to create and destroy proxies, start and stop proxies, add ProxyUsers to proxies, create filters and callbacks for users, and to host a web interface which can be used to view sessions in real time.

It also provides a socket that can be used to remotely manage the controller. The remote tool must have the same preshared key. The key is used to send HMAC-signed JSON blobs for execution by the controller.

func LoadControllerConfigFromFile

func LoadControllerConfigFromFile(filepath string, signer ssh.Signer) (error, *ProxyController)

func (*ProxyController) ActivateProxy

func (controller *ProxyController) ActivateProxy(proxyID uint64) error

func (*ProxyController) AddChannelFilterToUser

func (controller *ProxyController) AddChannelFilterToUser(proxyID uint64, username, password string, function *ChannelFilterFunc) (error, string)

func (*ProxyController) AddEventCallbackToUser

func (controller *ProxyController) AddEventCallbackToUser(proxyID uint64, username, password string, callback *EventCallback) (error, string)

func (*ProxyController) AddExistingProxy

func (controller *ProxyController) AddExistingProxy(proxy *ProxyContext) uint64

func (*ProxyController) AddProxy

func (controller *ProxyController) AddProxy(proxy *ProxyContext) uint64

func (*ProxyController) AddProxyFromJSON

func (controller *ProxyController) AddProxyFromJSON(data []byte) (error, uint64)

func (*ProxyController) AddUserToProxy

func (controller *ProxyController) AddUserToProxy(proxyID uint64, user *ProxyUser) (error, string)

func (*ProxyController) CreateProxy

func (controller *ProxyController) CreateProxy() uint64

func (*ProxyController) CreateSessionViewer

func (controller *ProxyController) CreateSessionViewer(proxyID uint64, username, password, sessionKey string) (error, *proxySessionViewer)

func (*ProxyController) CreateUserSessionViewer

func (controller *ProxyController) CreateUserSessionViewer(proxyID uint64, username, password string) (error, *proxySessionViewer)

func (*ProxyController) DeactivateProxy

func (controller *ProxyController) DeactivateProxy(proxyID uint64) error

func (*ProxyController) DestroyProxy

func (controller *ProxyController) DestroyProxy(proxyID uint64) (err error)

func (*ProxyController) ExportControllerAsJSON

func (controller *ProxyController) ExportControllerAsJSON() ([]byte, error)

func (*ProxyController) GetNextProxyID

func (controller *ProxyController) GetNextProxyID() uint64

func (*ProxyController) GetProxy

func (controller *ProxyController) GetProxy(proxyID uint64) (proxy *ProxyContext, err error)

func (*ProxyController) GetProxyViewerBySessionKey

func (controller *ProxyController) GetProxyViewerBySessionKey(proxyID uint64, sessionKey string) (error, *proxySessionViewer)

func (*ProxyController) GetProxyViewerByUsername

func (controller *ProxyController) GetProxyViewerByUsername(proxyID uint64, username string) (error, *proxySessionViewer)

func (*ProxyController) GetProxyViewerByViewerKey

func (controller *ProxyController) GetProxyViewerByViewerKey(proxyID uint64, viewerKey string) (error, *proxySessionViewer)

func (*ProxyController) GetProxyViewers

func (controller *ProxyController) GetProxyViewers(proxyID uint64) (error, map[string]*proxySessionViewer)

func (*ProxyController) GetProxyViewersAsList

func (controller *ProxyController) GetProxyViewersAsList(proxyID uint64) (error, []*proxySessionViewer)

func (*ProxyController) GetProxyViewersBySessionKey

func (controller *ProxyController) GetProxyViewersBySessionKey(proxyID uint64, sessionKey string) (error, []*proxySessionViewer)

func (*ProxyController) GetProxyViewersByUsername

func (controller *ProxyController) GetProxyViewersByUsername(proxyID uint64, username string) (error, []*proxySessionViewer)

func (*ProxyController) Initialize

func (controller *ProxyController) Initialize()

func (*ProxyController) InitializeSocket

func (controller *ProxyController) InitializeSocket()

func (*ProxyController) Listen

func (controller *ProxyController) Listen()

func (*ProxyController) RemoveChannelFilterFromUser

func (controller *ProxyController) RemoveChannelFilterFromUser(proxyID uint64, username, password string, function *ChannelFilterFunc) error

func (*ProxyController) RemoveChannelFilterFromUserByKey

func (controller *ProxyController) RemoveChannelFilterFromUserByKey(proxyID uint64, username, password, key string) error

func (*ProxyController) RemoveEventCallbackFromUser

func (controller *ProxyController) RemoveEventCallbackFromUser(proxyID uint64, username, password string, callback *EventCallback) error

func (*ProxyController) RemoveEventCallbackFromUserByKey

func (controller *ProxyController) RemoveEventCallbackFromUserByKey(proxyID uint64, username, password, key string) error

func (*ProxyController) RemoveUserFromProxy

func (controller *ProxyController) RemoveUserFromProxy(proxyID uint64, username, password string) error

func (*ProxyController) StartProxy

func (controller *ProxyController) StartProxy(proxyID uint64) error

func (*ProxyController) StartWebServer

func (controller *ProxyController) StartWebServer() error

func (*ProxyController) Stop

func (controller *ProxyController) Stop()

func (*ProxyController) StopProxies

func (controller *ProxyController) StopProxies()

func (*ProxyController) StopProxy

func (controller *ProxyController) StopProxy(proxyID uint64) error

func (*ProxyController) StopWebServer

func (controller *ProxyController) StopWebServer()

func (*ProxyController) UpdateProxiesWithCurrentLogger

func (controller *ProxyController) UpdateProxiesWithCurrentLogger(overwrite bool)

func (*ProxyController) UseNewLogger

func (controller *ProxyController) UseNewLogger(logger LoggerInterface)

func (*ProxyController) WriteControllerConfigToFile

func (controller *ProxyController) WriteControllerConfigToFile(filepath string) error

type ProxyControllerSocket

type ProxyControllerSocket interface {
	ListenAndServe(host string, handler ProxyControllerSocketHandler) error
	Stop()
	IsPlaintext() bool
}

type ProxyControllerSocketClient

type ProxyControllerSocketClient interface {
	SendLine(data []byte) error
	ReadLine() ([]byte, error)
}

type ProxyControllerSocketTCP

type ProxyControllerSocketTCP struct {
	TLSCert string
	TLSKey  string
	// contains filtered or unexported fields
}

The ProxyControllerSocket is either a socket or a websocket. In either case, the socket can be wrapped in TLS.

A client connects to the socket and sends authenticated messages to ProxyController remotely.

It is the Controller's job to create a valid key when it is required for TLS sessions.

func (*ProxyControllerSocketTCP) IsPlaintext

func (socket *ProxyControllerSocketTCP) IsPlaintext() bool

func (*ProxyControllerSocketTCP) ListenAndServe

func (socket *ProxyControllerSocketTCP) ListenAndServe(host string, handler ProxyControllerSocketHandler) error

func (*ProxyControllerSocketTCP) Stop

func (socket *ProxyControllerSocketTCP) Stop()

type ProxyControllerSocketTCPClient

type ProxyControllerSocketTCPClient struct {
	net.Conn
}

func (*ProxyControllerSocketTCPClient) ReadLine

func (client *ProxyControllerSocketTCPClient) ReadLine() ([]byte, error)

func (*ProxyControllerSocketTCPClient) SendLine

func (client *ProxyControllerSocketTCPClient) SendLine(data []byte) error

type ProxyControllerSocketWeb

type ProxyControllerSocketWeb struct {
	TLSCert string
	TLSKey  string
	// contains filtered or unexported fields
}

func (*ProxyControllerSocketWeb) IsPlaintext

func (socket *ProxyControllerSocketWeb) IsPlaintext() bool

func (*ProxyControllerSocketWeb) ListenAndServe

func (socket *ProxyControllerSocketWeb) ListenAndServe(host string, handler ProxyControllerSocketHandler) error

func (*ProxyControllerSocketWeb) Stop

func (socket *ProxyControllerSocketWeb) Stop()

type ProxyControllerSocketWebClient

type ProxyControllerSocketWebClient struct {
	websocket.Conn
}

func (*ProxyControllerSocketWebClient) ReadLine

func (client *ProxyControllerSocketWebClient) ReadLine() ([]byte, error)

func (*ProxyControllerSocketWebClient) SendLine

func (client *ProxyControllerSocketWebClient) SendLine(data []byte) error

type ProxyUser

type ProxyUser struct {
	Username       string
	Password       string
	RemoteHost     string
	RemoteUsername string
	RemotePassword string
	EventCallbacks []*EventCallback `json:"-"`
	// contains filtered or unexported fields
}

A ProxyUser defines the authenticating username and password a client connecting to the proxy must use to authenticate to this user.

Upon successful authentication, the user is proxied to the RemoteHost using the specified RemoteUsername and RemotePassword.

EventCallbacks can be specified via the ProxyController to provide anonymous functions that can be executed on certain events as they occur in a given session. Callbacks occur in goroutines and do not block. An example callback might be a function that calls back to a web hook whenever a specific string is seen.

channelFilters are also specified via the ProxyController and are executed on messages in an established session. They receive data as an argument and return modified data on the other end. These occur in series and block the transfer of data through the proxy. ChannelFilters do not currently distinguish between inbound and outbound traffic. Consequently both ends of the session will be filtered.

func (*ProxyUser) AddChannelFilter

func (user *ProxyUser) AddChannelFilter(function *ChannelFilterFunc) int

func (*ProxyUser) AddEventCallback

func (user *ProxyUser) AddEventCallback(callback *EventCallback) int

func (*ProxyUser) GetKey

func (user *ProxyUser) GetKey() string

func (*ProxyUser) RemoveChannelFilter

func (user *ProxyUser) RemoveChannelFilter(function *ChannelFilterFunc)

func (*ProxyUser) RemoveEventCallback

func (user *ProxyUser) RemoveEventCallback(callback *EventCallback)

type SessionContext

type SessionContext struct {
	// contains filtered or unexported fields
}

Every event is also written to a log file as it occurs.

func (*SessionContext) AddEvent

func (session *SessionContext) AddEvent(event *SessionEvent) *SessionEvent

func (*SessionContext) End

func (session *SessionContext) End()

func (*SessionContext) GetID

func (session *SessionContext) GetID() string

func (*SessionContext) GetTimeOffset

func (session *SessionContext) GetTimeOffset() int64

func (*SessionContext) HandleChannels

func (session *SessionContext) HandleChannels(dest_conn ssh.Conn, channels <-chan ssh.NewChannel)

func (*SessionContext) HandleEvent

func (session *SessionContext) HandleEvent(event *SessionEvent)

func (*SessionContext) InfoAsJSON

func (session *SessionContext) InfoAsJSON() string

func (*SessionContext) LogEvent

func (session *SessionContext) LogEvent(event *SessionEvent)

func (*SessionContext) MakeNewSignal

func (session *SessionContext) MakeNewSignal() chan int

func (*SessionContext) RemoveSignal

func (session *SessionContext) RemoveSignal(signal chan int)

type SessionEvent

type SessionEvent struct {
	Type           string `json:"type"`
	Key            string `json:"key,omitempty"`
	StartTime      int64  `json:"start,omitempty"`
	StopTime       int64  `json:"stop,omitempty"`
	Length         int64  `json:"length,omitempty"`
	TimeOffset     int64  `json:"offset,omitempty"`
	Direction      string `json:"direction,omitempty"`
	Size           int    `json:"size,omitempty"`
	Data           []byte `json:"data,omitempty"`
	ClientHost     string `json:"client_host,omitempty"`
	ServHost       string `json:"server_host,omitempty"`
	Username       string `json:"username,omitempty"`
	Password       string `json:"password,omitempty"`
	TermRows       uint32 `json:"term_rows,omitempty"`
	TermCols       uint32 `json:"term_cols,omitempty"`
	ChannelType    string `json:"channel_type,omitempty"`
	ChannelData    []byte `json:"channel_data,omitempty"`
	RequestType    string `json:"request_type,omitempty"`
	RequestPayload []byte `json:"request_payload,omitempty"`
	ChannelID      int    `json:"channel_id,omitempty"`
	RequestID      int    `json:"request_id,omitempty"`
}

SessionEvents are the meat of an SSH Session. They track the start and stop of a session, when new requests or channels are created, when a window is resized, and when data is transmitted as a message.

func (*SessionEvent) ToJSON

func (event *SessionEvent) ToJSON() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL