ssh_mesh

package module
v0.0.0-...-9c75cda Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2024 License: Apache-2.0 Imports: 28 Imported by: 7

README

ssh-mesh

This is an opinionated SSH library and L4 proxy, with a Istio-like certificate and JWT based authentication and providing a secure L4 transport.

The implementation is compatible with OpenSSH, dropbear and other libraries and clients/servers.

Special features for sshm in gateway mode:

  • allows multiple clients to remote forward 22, 80 and 443
  • allows the password to be a JWT token with audience ssh://HOSTNAME, issued by one of the configured issuers (with normalization for k8s and google tokens)
  • certificate based authentication

In progress/TODO:

  • auto-register the forwarding clients in EndpointSlice and support sharding (for scale). Until this is done - a single (large) instance must be run per IP. In K8S it means 1 replica if LoadBalancer service is used.

Special features for sshm in workload mode:

  • auto-register the forwarding ports and maintains connection. This is optional and should be used for CloudRun or home machines behind a firewall.
  • can chain a second command, so it can be added to a docker image and Pod.
  • includes a sshd server and exec/shell for the configured owner key, equivalent to running openssh or dropbear ssh server with custom config and as regular user.

A SSH CA maintains a root CA (backed by a k8s or other Secret), and signs host and user certificates. The format has same information as Istio Spiffe, a trust domain, namespace and service account, but 2 certificates are issued, one for server and one for client (with same key). The certificates can be generated from a JWT with a trusted issuer, mapping the "sub" claim. Any other SSH CA or keygen can be used to generate certs.

For K8S, the identity will be:

${KSA}@${Namespace}.${TrustDomain} ${KSA}.${Namespace}.svc.${TrustDomain}

TODO: watch Service and ServiceEntry and allow KSA configured to get cert for the service.

WIP: Mesh communication

Client will initiate a remote forward, with *:MESH_PORT address. Server will accept forward request of this type but will not open new ports.

Depending on MESH_PORT, SSH-mesh Gateway it should support Ztunnel HBONE protocol, HAProxy or SNI routing. Port 22, 80 and 443 are multiplexing the expected protocols.

Automatic certificate signing

This is similar with Istiod/Citadel signing of workload certificates, but for SSH certificates. To simplify the deployment and optimize strtup time, the gate includes cert signing code - but a separate CA can also be used.

Usage with Openssh/dropbear

Useful ssh args:

  • "-N" - don't start a shell/terminal - just port forwards
  • UserKnownHostsFile /dev/null - don't save the key
  • StrictHostKeyChecking=no - don't check server key (for example if a tunnel authenticates)
  • "-F /dev/null" - ignore host config
export SSH_ASKPASS=... # script doing /usr/bin/curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity?audience=ssh://

ssh -v  -p 15022 -i id_ecdsa  -o "UserKnownHostsFile known_hosts" costin@localhost

ssh -v -p 15022 s.webinf.duckdns.org -R NAME:22:localhost:22 

# To disable host checking:
# -F /dev/null -o "UserKnownHostsFile /dev/null" -o StrictHostKeyChecking=no

Alternatives

  • use an extension is appealing - for example allow servers to initiate direct-tcpip channels will make the code simpler. However interop with existing ssh tools is the main goal - otherwise H2/H3 should be used.

  • provide a rich API with support for global requests, extensions, etc - nice but not required.

SSH certificates - manually


ssh-keygen -t ecdsa -f ca 

ssh-keygen -f user-key -t ecdsa 
ssh-keygen -s ca -I user@domain -n user,honda -V +1y user-key.pub

# Host config
ssh-keygen -s ca -I host.example.com -h -n host,host.example.com -V +1y ssh_host_rsa_key.pub

#  /etc/ssh/sshd_config
# TrustedUserCAKeys /etc/ssh/ssh_user_key.pub
# HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub

# .ssh/known-hosts
# @cert-authority *.example.com ecdsa-sha2-nistp256 AAAAE...=  



# Debug - print cert
ssh-keygen -L -f id_ecdsa-cert.pub



Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DirectTCPIPHandler

func DirectTCPIPHandler(ctx context.Context, mux *SSHSMux, srv *SSHMesh, newChan ssh.NewChannel)

DirectTCPIPHandler is for 'direct-tcpip' channel type, to support client-originated reverseForwards. It runs on the server side.

See RFC 4254 7.2 RFC 4254 7.2" aria-label="Go to See RFC 4254 7.2">¶

- When client starts with a -L CPORT:host:port, and connects to CPORT - Also when client uses socks for dynamic reverseForwards (-D) - jump (-J)

If the destination port is 22/15022 - existing connections can be used to avoid an extra TCP connection.

Laddr is typically 127.0.0.1 (unless ssh has an open socks, and other machines use it)

func KeysEqual

func KeysEqual(ak, bk ssh.PublicKey) bool

KeysEqual is constant time compare of the keys to avoid timing attacks.

func SessionHandler

func SessionHandler(ctx context.Context, sconn *SSHSMux, newChannel ssh.NewChannel)

Basic untrusted session handler.

Types

type Command

type Command struct {
	Run func(env map[string]string, args []string, in io.Reader, out io.WriteCloser, err io.WriteCloser)
}

type KV

type KV struct {
	Key, Value string
}

type Mux

type Mux struct {
	// LastSeen    time.Time
	ConnectTime time.Time

	// network stream
	// May be an original con with net.Conn with remote/local addr
	NetConn io.ReadWriteCloser
}

Mux represents a TCP (or other protocol) connection with multiplexed streams on top. It can be used for SSH client, server, H2 and many protocols multiplexing streams.

type Proxy

type Proxy struct {
	OutBytes, InBytes int64
	OutErr, InErr     error
	// contains filtered or unexported fields
}

func (*Proxy) ProxyTo

func (p *Proxy) ProxyTo(ch io.ReadWriteCloser) error

type RemoteExec

type RemoteExec struct {
	ssh.Channel
	// contains filtered or unexported fields
}

RemoteExec is a "session" channel.

type SSHCA

type SSHCA struct {
	Signer ssh.Signer
}

func NewSSHCA

func NewSSHCA(privk crypto.PrivateKey) *SSHCA

func (*SSHCA) AuthorizedKey

func (s *SSHCA) AuthorizedKey() string

type SSHCMux

type SSHCMux struct {
	Mux
	*SSHMesh

	// If set, a persistent connection will be maintained and
	// - mux reverseForwards registered for 22, 80, 443
	// - accept streams and trust auth
	Waypoint bool

	// TODO: CIDR/Networks
	ReverseForwards map[string]string

	LastConnected time.Time

	// The SSH Conn object
	SSHConn ssh.Conn

	// TODO: limit by domain ?
	AuthorizedCA []ssh.PublicKey

	// Same as the transport
	CertChecker *ssh.CertChecker

	SSHClient *ssh.Client

	// Last received remote key (should be a Certificate)
	RemoteKey ssh.PublicKey
	// contains filtered or unexported fields
}

SSHCMux is a multiplexed client connection to a single destination. That corresponds to a H2 connection - it is possible to have multiple SSHCMux connections to the same destination at the same time.

func (*SSHCMux) ClientSession

func (sshc *SSHCMux) ClientSession()

func (*SSHCMux) Dial

func (sc *SSHCMux) Dial(ctx context.Context, addr string) error

Dial opens one TCP or H2 connection to addr. It blocks until the SSH handshake is done.

func (*SSHCMux) DialConn

func (sc *SSHCMux) DialConn(ctx context.Context, tcon net.Conn, addr string) error

func (*SSHCMux) Exec

func (ssht *SSHCMux) Exec(cmd string, env map[string]string) (*RemoteExec, error)

Exec opens a client session channel for a command.

func (*SSHCMux) ListenTCP

func (c *SSHCMux) ListenTCP(domain string, port uint32) (uint32, error)

ListenTCP requests the remote peer open a listening socket on port.

Regular SSH servers don't multiplex on port. RFC4254: "" means that connections are to be accepted on all protocol

   families supported by the SSH implementation.

o  "0.0.0.0" means to listen on all IPv4 addresses.

o  "::" means to listen on all IPv6 addresses.

o  "localhost" means to listen on all protocol families supported by
   the SSH implementation on loopback addresses only ([RFC3330] and
   [RFC3513]).

o  "127.0.0.1" and "::1" indicate listening on the loopback
   interfaces for IPv4 and IPv6, respectively.

Port 0 is usually supported.

func (*SSHCMux) OpenStream

func (c *SSHCMux) OpenStream(n string, data []byte) (*Stream, error)

OpenStream creates a new stream. This uses the same channel in both directions.

func (*SSHCMux) Proxy

func (sc *SSHCMux) Proxy(ch io.ReadWriteCloser, dstp string, s string)

Proxy an incoming stream to a destination, for remotely accepted steams (-R) TODO: optimize.

func (*SSHCMux) StayConnected

func (sshc *SSHCMux) StayConnected(addr string)

StayConnected will maintain an active connection, typically with a jump host.

'addr' is the IP:port to connect to - not the 'canonical' service.

type SSHMesh

type SSHMesh struct {
	Mesh *meshauth.Mesh

	// Address to listen on as SSH. Will default to 14022 for regular nodes and
	// 15022 for gateways.
	Address     string           `json:"sshd_addr,omitempty"`
	Listener    net.Listener     `json:"-"`
	CertChecker *ssh.CertChecker `json:"-"`

	// Multiplexed clients to upstream hosts, by destination.
	// May disconnect - the same client is reused.
	Clients sync.Map // [string,*SSHCMux]

	ConnectErrors atomic.Int64

	// Map of public key to user ID.
	// Key is the marshalled public key (from authorized_keys), value is the user ID (comment)
	UsersKeys map[string]string `json:"-"`

	// Signer is the 'workload identity' signer. It is using the main workload
	// private key (workload key), but with a host certificate.
	Signer ssh.Signer `json:"-"`

	// SignerClient is a client workload identity - using the SA cert when
	// a CA is used. Otherwise, same as Signer.
	SignerClient ssh.Signer `json:"-"`

	// Forward is a function that will proxy a stream to a destination.
	// If missing, it will be dialed.
	// Used on a server for all client forwarding - except locally connected clients.
	Forward func(context.Context, string, io.ReadWriteCloser) `json:"-"`

	// WIP: Internally defined commands.
	InternalCommands map[string]*Command `json:"-"`

	// Root CA keys - will be authorized to connect and create tunnels, not get shell.
	AuthorizedCA []ssh.PublicKey `json:"-"`

	// WIP: Custom channel handlers.
	ChannelHandlers map[string]func(ctx context.Context, sconn *SSHSMux, newChannel ssh.NewChannel) `json:"-"`

	// TokenChecker will verify the password field - as a JWT or other forms.
	TokenChecker func(password string) (claims map[string]string, e error) `json:"-"`

	// Deprecated - Credentials
	CertHost string `json:"ssh.crt,omitempty"`
	// Deprecated - Credentials
	CertClient string `json:"ssh-client.crt,omitempty"`

	// AuthorizedKeys is the same as authorized_keys file.
	// The keys are used as 'trusted sources' for authentication. Any user key can be used for shell/debug access.
	// The CA keys are allowed to connect - but can get a shell only if 'role=admin' is present in the cert.
	//
	// If empty, the SSH_AUTHORIZED_KESY env is used, falling back to authorized_keys in current dir and $HOME/.ssh
	AuthorizedKeys string `json:"authorized_keys,omitempty"`

	sync.Mutex
	// contains filtered or unexported fields
}

SSHMesh is a minimal L4S (ambient) mesh implementation based on SSH, and compatible with standard SSH clients and servers.

func NewSSHMesh

func NewSSHMesh(ma *meshauth.Mesh) (*SSHMesh, error)

NewSSHMesh creates the SSHMesh object. Must call SetKeys() or FromEnv() before using it.

If the key is missing, a self-signed key is generated.

Extensions compared to regular sshd: - can use JWT tokens as password - based on Issuer config. - multiplex forwarded ports 22, 80, 443 - optimized for the use of a CA for both client and server.

TODO: As a server, it can also prove its workload ID with a JWT and jumpstart known_hosts !

func (*SSHMesh) AddAuthorizedFile

func (ssht *SSHMesh) AddAuthorizedFile(auth []byte)

AddAuthorizedFile will load the ssh "authorized_files" content.

All CAs are added separately, and will also be used for host authorization. The 'comment' field is saved - and will be used as 'user' when public key auth is using that key.

func (*SSHMesh) Client

func (ss *SSHMesh) Client(ctx context.Context, dst string) (*SSHCMux, error)

Client returns a SSH client for a destination. It may be disconnected - first call is always disconnected.

func (*SSHMesh) FromEnv

func (ss *SSHMesh) FromEnv(ctx context.Context, base string)

func (*SSHMesh) HandleServerConn

func (ssht *SSHMesh) HandleServerConn(nConn net.Conn)

Handles a connection as SSH server, using a net.Conn - which might be tunneled over other transports. SSH handles multiplexing and packets.

func (*SSHMesh) InitMux

func (st *SSHMesh) InitMux(mux *http.ServeMux)

InitMux add the H2 functions

func (*SSHMesh) ListenAndStart

func (ssht *SSHMesh) ListenAndStart() (net.Listener, error)

func (*SSHMesh) Proxy

func (srv *SSHMesh) Proxy(ctx context.Context, dest string, ch io.ReadWriteCloser) (*Proxy, error)

func (*SSHMesh) PubString

func (ssht *SSHMesh) PubString()

func (*SSHMesh) SetKeys

func (s *SSHMesh) SetKeys(priv string, hostc string, clientc string) (*SSHMesh, error)

func (*SSHMesh) Sign

func (s *SSHMesh) Sign(pub ssh.PublicKey, certType uint32, names []string) ([]byte, *ssh.Certificate, error)

Sign will sign a client or server certificate.

Each host can sign - the resulting cert should be under the host trust.

func (*SSHMesh) SignCert

func (s *SSHMesh) SignCert(cert *ssh.Certificate) ([]byte, error)

SignCert signs an arbitrary certificate.

func (*SSHMesh) Start

func (ssht *SSHMesh) Start(ctx context.Context) error

type SSHSMux

type SSHSMux struct {
	Mux

	// ServerConn - also has Permission
	ServerConn *ssh.ServerConn

	// Includes the private key of this node
	SSHServer *SSHMesh

	RemoteKey ssh.PublicKey

	// For server-side sessions, this is the last active session.
	// With multiplexing, multiple sessions may be in effect.
	SessionStream ssh.Channel

	FQDN string
}

SSHSMux is a server ssh connection.

func (*SSHSMux) Filecmd

func (c *SSHSMux) Filecmd(request *sftp.Request) error

Called for Methods: Setstat, Rename, Rmdir, Mkdir, Link, Symlink, Remove

func (*SSHSMux) Filelist

func (c *SSHSMux) Filelist(request *sftp.Request) (sftp.ListerAt, error)

func (*SSHSMux) Fileread

func (c *SSHSMux) Fileread(request *sftp.Request) (io.ReaderAt, error)

func (*SSHSMux) Filewrite

func (c *SSHSMux) Filewrite(request *sftp.Request) (io.WriterAt, error)

func (*SSHSMux) OpenStream

func (c *SSHSMux) OpenStream(n string, data []byte) (*Stream, error)

OpenStream creates a new stream. This uses the same channel in both directions.

type Stream

type Stream struct {
	ssh.Channel
	// contains filtered or unexported fields
}

Stream is a client or server stream - 'Channel' in SSH terms.

Also implements gossh.Channel - add SendRequest and Stderr, as well as CloseWrite

Directories

Path Synopsis
cmd
h2t
sshd Module
sshgate Module
nio
bufferv2
Package bufferv2 provides the implementation of a non-contiguous buffer that is reference counted, pooled, and copy-on-write.
Package bufferv2 provides the implementation of a non-contiguous buffer that is reference counted, pooled, and copy-on-write.
bufferv2/bits
Package bits includes all bit related types and operations.
Package bits includes all bit related types and operations.
syscall
Package syscall provides functionalities that grpc uses to get low-level operating system stats/info.
Package syscall provides functionalities that grpc uses to get low-level operating system stats/info.
pkg

Jump to

Keyboard shortcuts

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