Skip to content
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

Refactor agentprotocol #373

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions agentprotocol/Connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package agentprotocol

type Connection interface {
// Read reads data from the connection. The blocking nature of this call depends on the underlying communication medium
Read(p []byte) (n int, err error)
// Read reads data from the connection. The blocking nature of this call depends on the underlying communication medium
Write(data []byte) (n int, err error)
// Close requests to close an active connection
Close() error
// CloseImmediately closes an active connection without waiting for the other side to acknowledge
CloseImmediately() error
// Accept accepts a pending connection request. It must be called before any Read/Write functions can be called on the connection
Accept() error
// Reject rejects a pending connection request and closes the connection.
Reject() error
// Details returns the details of a connection request. It can be called to gain more information about a connection before an Accept/Reject action is made
Details() NewConnectionPayload
}
69 changes: 69 additions & 0 deletions agentprotocol/ForwardCtx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package agentprotocol

import "io"

type ForwardCtx interface {
// NewConnectionTCP requests the other side to connect to a specified address/host and port combination and forward all data from the returned ReadWriteCloser to it.
//
// connectedAddress is the address that the connection requested to connect to, is it used by the receiving side to initiate the connection to the desired address.
// connectedPort is the port that the connection requested to connect to, is it used by the receiving side to initiate the connection to the desired port.
// origAddress is the originator address of the connection. It can be used by the receiving side to decide whether to accept this connection.
// origAddress is the originator port of the connection. It can be used by the receiving side to decide whether to accept this connection.
// closeFunc is a callback function that is called when the connection is called to perform cleanup of the backing connection.
NewConnectionTCP(
connectedAddress string,
connectedPort uint32,
origAddress string,
origPort uint32,
closeFunc func() error,
) (io.ReadWriteCloser, error)
// NewConnectionUnix requests the other side to connect to a specified unix and forward all data from the returned ReadWriteCloser to it.
//
// path is the path of the unix socket to connect to.
// closeFunc is a callback function that is called when the connection is called to perform cleanup of the backing connection.
NewConnectionUnix(
path string,
closeFunc func() error,
) (io.ReadWriteCloser, error)

// StartServer initializes the ForwardCtx in server mode which waits for information from the other side about the function it needs to perform. It returns the connection type the other side requests and additional information in the setupPacket. Additionally, a Connection channel, connChan, is returned that provides connection requests from the other side of the connection.
StartServer() (connectionType uint32, setupPacket SetupPacket, connChan chan Connection, err error)

// StartClientForward initializes the ForwardCtx in client mode and informs the server that the client is going to be the connection requestor (Direct Forward). A connection channel is returned that informs the server of connection requests by the client however in this mode it is a assumed that the server sends no connection request so the sane behaviour is to reject all connections. In this mode, the client can start new connections on the server using the NewConnection* function family.
StartClientForward() (chan Connection, error)

// StartX11ForwardClient initializes the ForwardCtx in client mode and informs the server to start an X11 server and forward all X11 connections to the client.
//
// singleConnection is the X11 singleConnection parameter that requests to only accept the first connection (X11 window) and no more.
// screen is the X11 screen number.
// authProtocol is the X11 auth protocol.
// authCookie is the X11 auth cookie.
StartX11ForwardClient(
singleConnection bool,
screen string,
authProtocol string,
authCookie string,
) (chan Connection, error)

// StartReverseForwardClient initializes the ForwardCtx in client mode and informs the server to start listening for connections on the requested host and port. Once a connection is received a new connection is created and sent through the Connection channel.
//
// bindHost is the host to listen on connections on.
// bindPort is the port to listen on connections on.
// singleConnection is a flag that requests to stop listening for new connections after the first one.
StartReverseForwardClient(bindHost string, bindPort uint32, singleConnection bool) (chan Connection, error)

// StartReverseForwardClient initializes the ForwardCtx in client mode and informs the server to start listening for connections on the requested unix socket. Once a connection is received a new connection is created and sent through the Connection channel.
//
// path is the path to the unix socket to listen on.
// singleConnection is a flag that requests to stop listening for new connections after the first one.
StartReverseForwardClientUnix(path string, singleConnection bool) (chan Connection, error)

// NoMoreConnections informs the other side that it should not accept any more connection requests from it. It is used as a forwarding security feature in cases where it's clear there will only be one connection.
NoMoreConnections() error

// WaitFinish blocks until NoMoreConnections has been received and all active connections have been closed.
WaitFinish()

// Kill closes the ForwardCtx immediately and terminates all connections.
Kill()
}
12 changes: 6 additions & 6 deletions agentprotocol/NewForwardCtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package agentprotocol
import (
"io"

log "go.containerssh.io/libcontainerssh/log"
log "go.containerssh.io/libcontainerssh/log"
)

func NewForwardCtx(fromBackend io.Reader, toBackend io.Writer, logger log.Logger) *ForwardCtx {
return &ForwardCtx{
func NewForwardCtx(fromBackend io.Reader, toBackend io.Writer, logger log.Logger) ForwardCtx {
return &forwardCtx{
fromBackend: fromBackend,
toBackend: toBackend,
logger: logger,
toBackend: toBackend,
logger: logger,
}
}
}
28 changes: 14 additions & 14 deletions agentprotocol/Protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package agentprotocol

const (
CONNECTION_TYPE_X11 = iota
CONNECTION_TYPE_PORT_FORWARD = iota
CONNECTION_TYPE_PORT_DIAL = iota
CONNECTION_TYPE_SOCKET_FORWARD = iota
CONNECTION_TYPE_SOCKET_DIAL = iota
CONNECTION_TYPE_PORT_FORWARD
CONNECTION_TYPE_PORT_DIAL
CONNECTION_TYPE_SOCKET_FORWARD
CONNECTION_TYPE_SOCKET_DIAL
)

const (
PROTOCOL_TCP string = "tcp"
PROTOCOL_TCP string = "tcp"
PROTOCOL_UNIX string = "unix"
)

Expand All @@ -24,10 +24,10 @@ const (
)

type SetupPacket struct {
ConnectionType uint32
BindHost string
BindPort uint32
Protocol string
ConnectionType uint32
BindHost string
BindPort uint32
Protocol string

Screen string
SingleConnection bool
Expand All @@ -36,16 +36,16 @@ type SetupPacket struct {
}

type NewConnectionPayload struct {
Protocol string
Protocol string

ConnectedAddress string
ConnectedPort uint32
OriginatorAddress string
OriginatorPort uint32
}

type Packet struct {
Type int
ConnectionId uint64
Payload []byte
Type int
ConnectionID uint64
Payload []byte
}
27 changes: 27 additions & 0 deletions agentprotocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# The ContainerSSH Agent protocol

The ContainerSSH Agent protocol allows for forwarding and reverse-forwarding several types of connections within the container: X11, TCP, etc. The protocol is designed to be symmetrical in a way that both ends can request, accept/reject, and process connections and both ends have the same capabilities during a connection. However, there is a small client/server distinction while initializing the protocol in the initial exchange: The 'Client' (ContainerSSH) sends a `SetupPacket` to the 'Server' (Agent) that specificies the mode that the agent should initialize to (e.g. forward, reverse-forward, X11 forward etc).

## Concepts

The server and the client communicate over the standard input/output using the container APIs. (Docker and Kubernetes) The agent is running just like any other program would in the container.

### Server

The server in this context is the ContainerSSH agent. It waits for connection requests from the client (ContainerSSH) and opens the corresponding sockkets.

### Client

The client in this context is ContainerSSH, opening a connection by sending requests to the agent via the standard input/output using the container API (Docker or Kubernetes).

### Connection

A Connection is a bidirectional binary communication between the server and the client. Multiple number of connections can be active at any given time and both sides (client/server) have the capacity to request a new connection. Connections are identified by a ConnectionID and each packet includes the ConnectionID to associate it with a connection. The state of connections is detailed in the following flow graph where the nodes represent the valid connection states and the edges are the actions/packets that affect the connection state.

![connection state diagram](./images/cssh-agent.png)

When a connection is initiated it is in WAITINIT state until the other end issues either an Accept action, which results in a SUCCESS message and the connection starting or a Reject action whith results in an ERROR message and the connection closing. When a connection is in the STARTED state it can accept data and both sides can issue write() and read() calls to write and read from the connection. The blocking/non-blocking nature of these calls depends on the underlying communication medium. When a connection is closed from one end it is moved to the WAITCLOSE state until the other side acknowledges the close request. This is necessary to ensure that any leftover data sent after the close call is processed. Finally once the close request is acknowledged the connection is finally closed.

## Protocol

The protocol consists of messages sent in [CBOR-encoding](https://cbor.io/) in a back-to-back fashion. Other than the connection control packets described above there is additionally a 'No More Connections' packet that instructs the other side to stop accepting new connections. This is handled internally in the protocol library by closing the new connection channel.
Binary file added agentprotocol/images/cssh-agent.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.