Skip to content

Commit

Permalink
feat: SSH into containers through Node
Browse files Browse the repository at this point in the history
This allows to perform SSH'ing into containers by specifying only deal
and task identifiers independing of whereever those containers are being
run.

This is done by proxying the traffic through local Node.

The idea is to hijack to incoming TCP connection with further resolving
the real endpoint of a Worker where a container is being run by deal ID
using NPP with further traffic forwarding directly into it. The Worker
may not be having a public IP address.
  • Loading branch information
3Hren committed Aug 28, 2018
1 parent a2df119 commit c3eb11b
Show file tree
Hide file tree
Showing 14 changed files with 625 additions and 107 deletions.
2 changes: 2 additions & 0 deletions insonmnia/node/config.go
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/sonm-io/core/insonmnia/logging"
"github.com/sonm-io/core/insonmnia/matcher"
"github.com/sonm-io/core/insonmnia/npp"
"github.com/sonm-io/core/insonmnia/ssh"
"github.com/sonm-io/core/optimus"
"github.com/sonm-io/core/util/debug"
)
Expand All @@ -31,6 +32,7 @@ type Config struct {
Matcher *matcher.YAMLConfig `yaml:"matcher"`
Predictor *optimus.PredictorConfig `yaml:"predictor"`
Debug *debug.Config `yaml:"debug"`
SSH *ssh.ProxyServerConfig `yaml:"ssh"`
}

// NewConfig loads localNode config from given .yaml file
Expand Down
4 changes: 4 additions & 0 deletions insonmnia/node/mod.go
Expand Up @@ -62,6 +62,10 @@ func New(ctx context.Context, cfg *Config, options ...Option) (*Node, error) {
)
}

if cfg.SSH != nil {
serverOptions = append(serverOptions, WithSSH(*cfg.SSH, transportCredentials, remoteOptions.eth.Market(), log.Sugar()))
}

server, err := newServer(cfg.Node, services, serverOptions...)
if err != nil {
return nil, fmt.Errorf("failed to build Node instance: %s", err)
Expand Down
19 changes: 18 additions & 1 deletion insonmnia/node/options.go
Expand Up @@ -5,7 +5,9 @@ import (
"crypto/sha256"

"github.com/ethereum/go-ethereum/crypto"
"github.com/sonm-io/core/blockchain"
"github.com/sonm-io/core/insonmnia/auth"
"github.com/sonm-io/core/insonmnia/ssh"
"github.com/sonm-io/core/util/rest"
"github.com/sonm-io/core/util/xgrpc"
"go.uber.org/zap"
Expand Down Expand Up @@ -37,12 +39,14 @@ type serverOptions struct {
allowREST bool
optionsREST []rest.Option
exposeGRPCMetrics bool
sshProxy SSHServer
log *zap.Logger
}

func newServerOptions() *serverOptions {
return &serverOptions{
log: zap.NewNop(),
sshProxy: &ssh.NilSSHProxyServer{},
log: zap.NewNop(),
}
}

Expand Down Expand Up @@ -89,6 +93,19 @@ func WithGRPCServerMetrics() ServerOption {
}
}

func WithSSH(cfg ssh.ProxyServerConfig, credentials credentials.TransportCredentials, market blockchain.MarketAPI, log *zap.SugaredLogger) ServerOption {
return func(o *serverOptions) error {
server, err := ssh.NewSSHProxyServer(cfg, credentials, market, log)
if err != nil {
return err
}

o.sshProxy = server

return nil
}
}

func WithServerLog(log *zap.Logger) ServerOption {
return func(o *serverOptions) error {
o.log = log
Expand Down
12 changes: 10 additions & 2 deletions insonmnia/node/server.go
Expand Up @@ -54,6 +54,10 @@ type Services interface {
Run(ctx context.Context) error
}

type SSHServer interface {
Serve(ctx context.Context) error
}

// Server is a server for LocalNode instance.
//
// Its responsibility is to manage network part, i.e. creating TCP servers and
Expand All @@ -68,6 +72,7 @@ type Server struct {
// Servers for processing requests.
serverGRPC *grpc.Server
serverREST *rest.Server
serverSSH SSHServer

log *zap.SugaredLogger
}
Expand Down Expand Up @@ -111,7 +116,8 @@ func newServer(cfg nodeConfig, services Services, options ...ServerOption) (*Ser
GRPC: toLocalAddrs(listenersGRPC),
REST: toLocalAddrs(listenersREST),
},
services: services,
services: services,
serverSSH: opts.sshProxy,
}

if opts.allowGRPC {
Expand Down Expand Up @@ -178,7 +184,9 @@ func (m *Server) Serve(ctx context.Context) error {
wg.Go(func() error {
return m.services.Run(ctx)
})
// TODO: Also add debug server and ssh.
wg.Go(func() error {
return m.serverSSH.Serve(ctx)
})

<-ctx.Done()

Expand Down
6 changes: 6 additions & 0 deletions insonmnia/npp/dial.go
Expand Up @@ -4,6 +4,7 @@ package npp

import (
"context"
"fmt"
"net"
"time"

Expand Down Expand Up @@ -105,6 +106,11 @@ func (m *Dialer) DialContext(ctx context.Context, addr auth.Addr) (net.Conn, err
}
}

if m.relayDialer == nil {
log.Warn("failed to connect using NPP - all methods failed")
return nil, fmt.Errorf("failed to connect using NPP - all methods failed")
}

log.Debug("connecting using Relay")
channel := make(chan connTuple)
go func() {
Expand Down
12 changes: 11 additions & 1 deletion insonmnia/npp/options.go
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/sonm-io/core/insonmnia/npp/relay"
"github.com/sonm-io/core/insonmnia/npp/rendezvous"
"github.com/sonm-io/core/proto"
"go.uber.org/zap"
"google.golang.org/grpc/credentials"
)
Expand All @@ -23,6 +24,7 @@ type options struct {
nppMaxBackoffInterval time.Duration
relayListener *relay.Listener
relayDialer *relay.Dialer
protocol string
}

func newOptions() *options {
Expand All @@ -31,6 +33,7 @@ func newOptions() *options {
nppBacklog: 128,
nppMinBackoffInterval: 500 * time.Millisecond,
nppMaxBackoffInterval: 8000 * time.Millisecond,
protocol: sonm.DefaultNPPProtocol,
}
}

Expand All @@ -49,7 +52,7 @@ func WithRendezvous(cfg rendezvous.Config, credentials credentials.TransportCred
for _, addr := range cfg.Endpoints {
client, err := newRendezvousClient(ctx, addr, credentials)
if err == nil {
return newNATPuncher(ctx, cfg, client, o.log)
return newNATPuncher(ctx, cfg, client, o.protocol, o.log)
}
}

Expand Down Expand Up @@ -112,3 +115,10 @@ func WithRelayDialer(dialer *relay.Dialer) Option {
return nil
}
}

func WithProtocol(protocol string) Option {
return func(o *options) error {
o.protocol = protocol
return nil
}
}
7 changes: 5 additions & 2 deletions insonmnia/npp/puncher.go
Expand Up @@ -52,14 +52,15 @@ type natPuncher struct {

client *rendezvousClient
pending *lane.Queue
protocol string
listener net.Listener
listenerChannel chan connTuple

maxAttempts int
timeout time.Duration
}

func newNATPuncher(ctx context.Context, cfg rendezvous.Config, client *rendezvousClient, log *zap.Logger) (NATPuncher, error) {
func newNATPuncher(ctx context.Context, cfg rendezvous.Config, client *rendezvousClient, proto string, log *zap.Logger) (NATPuncher, error) {
// It's important here to reuse the Rendezvous client local address for
// successful NAT penetration in the case of cone NAT.
listener, err := reuseport.Listen(protocol, client.LocalAddr().String())
Expand All @@ -74,6 +75,7 @@ func newNATPuncher(ctx context.Context, cfg rendezvous.Config, client *rendezvou
log: log,
client: client,
pending: lane.NewQueue(),
protocol: proto,
listenerChannel: channel,
listener: listener,

Expand Down Expand Up @@ -165,7 +167,7 @@ func (m *natPuncher) resolve(ctx context.Context, addr common.Address) (*sonm.Re
}

request := &sonm.ConnectRequest{
Protocol: protocol,
Protocol: m.protocol,
PrivateAddrs: []*sonm.Addr{},
ID: addr.Bytes(),
}
Expand All @@ -185,6 +187,7 @@ func (m *natPuncher) publish(ctx context.Context) (*sonm.RendezvousReply, error)
}

request := &sonm.PublishRequest{
Protocol: m.protocol,
PrivateAddrs: []*sonm.Addr{},
}

Expand Down
11 changes: 11 additions & 0 deletions insonmnia/ssh/config.go
@@ -0,0 +1,11 @@
package ssh

import (
"github.com/sonm-io/core/insonmnia/npp"
)

// ProxyServerConfig specifies SSH proxy server configuration.
type ProxyServerConfig struct {
Addr string `yaml:"endpoint" required:"true"`
NPP npp.Config `yaml:"npp" required:"true"`
}

0 comments on commit c3eb11b

Please sign in to comment.