Compare commits
No commits in common. "39faf83c78212d034c7c503c0bba804d781b4058" and "90332cd1348539f6c6f9f542f62a706df791b130" have entirely different histories.
39faf83c78
...
90332cd134
@ -1,140 +0,0 @@
|
|||||||
# Enable or disable debug logging.
|
|
||||||
debug: true
|
|
||||||
# API settings.
|
|
||||||
api:
|
|
||||||
# Local port for Web API. Will be bound to localhost.
|
|
||||||
web_port: 25680
|
|
||||||
# Local port for plugin API. Will listen on all interfaces because it has auth.
|
|
||||||
plugin_port: 25681
|
|
||||||
# Docker client preferences.
|
|
||||||
docker:
|
|
||||||
# Extract client params from the environment.
|
|
||||||
from_env: true
|
|
||||||
# Cert path for the Docker client.
|
|
||||||
cert_path: ~
|
|
||||||
# Set it to false to disable TLS cert verification.
|
|
||||||
tls_verify: true
|
|
||||||
# Docker host. Can be useful for running containers alongside remote plugin (although it sounds weird to do so).
|
|
||||||
host: ~
|
|
||||||
# Docker version.
|
|
||||||
version: ~
|
|
||||||
# Default server to use if `sshpoke.server` is not specified in the target container labels.
|
|
||||||
default_server: mine
|
|
||||||
# Servers configuration.
|
|
||||||
servers:
|
|
||||||
# Server name.
|
|
||||||
- name: mine
|
|
||||||
# Server driver. Each driver has its own set of params. Supported drivers: ssh, plugin, null.
|
|
||||||
driver: ssh
|
|
||||||
params:
|
|
||||||
# SSH server address
|
|
||||||
address: "your1.server:2222"
|
|
||||||
# Remote port to be used for forwarding.
|
|
||||||
forward_port: 80
|
|
||||||
# This disables remote host resolution and forcibly uses server IP for remote host.
|
|
||||||
# It's the same as this syntax for sish: `ssh -R addr:80:localhost:80 your.sish.server`
|
|
||||||
# Set this to true if you're using sish, otherwise you'll get weird domains with IP's in them.
|
|
||||||
fake_remote_host: true
|
|
||||||
# Disables PTY request for this server.
|
|
||||||
nopty: true
|
|
||||||
# Requests interactive shell for SSH sessions. Should be `true` for the `commands`.
|
|
||||||
shell: false
|
|
||||||
# Authentication data.
|
|
||||||
auth:
|
|
||||||
# Authentication type. Supported types: key, password, passwordless
|
|
||||||
type: key
|
|
||||||
# Remote user
|
|
||||||
user: user
|
|
||||||
# Directory with SSH keys. ssh-config from this directory will be used if `keyfile` is not provided.
|
|
||||||
# Only some of the ssh-config attributes are used.
|
|
||||||
directory: "~/.ssh"
|
|
||||||
# Expose mode (multiple domains or single domain). Allowed values: single, multi.
|
|
||||||
mode: multi
|
|
||||||
# Keep-alive settings. Remove to disable keep-alive completely.
|
|
||||||
keepalive:
|
|
||||||
# Interval for keep-alive requests in seconds.
|
|
||||||
interval: 1
|
|
||||||
# How many attempts should fail to forcibly restart the connection.
|
|
||||||
max_attempts: 2
|
|
||||||
# Regular expression that will be used to extract domain from stdout & stderr. Useful for services like sish or
|
|
||||||
# localhost.run. `commands` output will also be parsed by this regex.
|
|
||||||
# With `!name` syntax you can use some built-in expressions:
|
|
||||||
# - !webUrl - any HTTP or HTTPS URL.
|
|
||||||
# - !httpUrl - any HTTP URL.
|
|
||||||
# - !httpsUrl - any HTTPS URL.
|
|
||||||
domain_extract_regex: "!httpsUrl"
|
|
||||||
# Host keys to prevent MITM. You can obtain those via `ssh-keyscan <address>` (specify `-p` for non-standard port).
|
|
||||||
# Always use '|' YAML syntax here (not '>') or sshpoke won't be able to parse keys.
|
|
||||||
host_keys: |
|
|
||||||
# ssh.neur0tx.site:2222 SSH-2.0-sish
|
|
||||||
# ssh.neur0tx.site:2222 SSH-2.0-sish
|
|
||||||
# ssh.neur0tx.site:2222 SSH-2.0-sish
|
|
||||||
[ssh.neur0tx.site]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEvxbqK0u8UjqEtrO/83GPS7MeoFp6C3+7KjOHd8+1GF
|
|
||||||
# ssh.neur0tx.site:2222 SSH-2.0-sish
|
|
||||||
# ssh.neur0tx.site:2222 SSH-2.0-sish
|
|
||||||
- name: ssh-demo-single-domain
|
|
||||||
driver: ssh
|
|
||||||
params:
|
|
||||||
auth:
|
|
||||||
type: key
|
|
||||||
user: user
|
|
||||||
directory: "~/.ssh"
|
|
||||||
keyfile: id_ed25519
|
|
||||||
address: "your2.server"
|
|
||||||
forward_port: 80
|
|
||||||
fake_remote_host: true
|
|
||||||
nopty: false
|
|
||||||
shell: true
|
|
||||||
mode: single
|
|
||||||
keepalive:
|
|
||||||
interval: 1
|
|
||||||
max_attempts: 2
|
|
||||||
domain_extract_regex: "!webUrl"
|
|
||||||
- name: ssh-demo-commands
|
|
||||||
driver: ssh
|
|
||||||
params:
|
|
||||||
address: "your3.server"
|
|
||||||
forward_port: 8080
|
|
||||||
auth:
|
|
||||||
type: key
|
|
||||||
user: user
|
|
||||||
directory: "~/.ssh"
|
|
||||||
mode: multi
|
|
||||||
keepalive:
|
|
||||||
interval: 1
|
|
||||||
max_attempts: 2
|
|
||||||
domain_extract_regex: "!webUrl"
|
|
||||||
# Commands that will be executed on the host.
|
|
||||||
commands:
|
|
||||||
# These commands will be executed after connect.
|
|
||||||
on_connect:
|
|
||||||
- echo https://`date +%s`.proxy.test
|
|
||||||
# These commands will be executed before disconnect.
|
|
||||||
on_disconnect:
|
|
||||||
- echo disconnect from `cat /etc/hostname`
|
|
||||||
- name: ssh-demo-with-password
|
|
||||||
driver: ssh
|
|
||||||
params:
|
|
||||||
address: "ssh.neur0tx.site"
|
|
||||||
forward_port: 8081
|
|
||||||
auth:
|
|
||||||
type: password
|
|
||||||
user: user
|
|
||||||
# Remote user password.
|
|
||||||
password: password
|
|
||||||
mode: multi
|
|
||||||
keepalive:
|
|
||||||
interval: 1
|
|
||||||
max_attempts: 2
|
|
||||||
domain_extract_regex: "!httpUrl"
|
|
||||||
commands:
|
|
||||||
on_connect:
|
|
||||||
- echo http://`date +%s`.proxy.test
|
|
||||||
- name: plugin-demo
|
|
||||||
driver: plugin
|
|
||||||
params:
|
|
||||||
# This token will be used by plugin while connecting to gRPC API.
|
|
||||||
token: key
|
|
||||||
- name: noop
|
|
||||||
# Null driver doesn't do anything. This driver will automatically be used for servers with invalid 'driver' value.
|
|
||||||
driver: null
|
|
@ -2,14 +2,12 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/config"
|
"github.com/Neur0toxine/sshpoke/internal/config"
|
||||||
@ -27,7 +25,6 @@ type SSH struct {
|
|||||||
base.Base
|
base.Base
|
||||||
params Params
|
params Params
|
||||||
auth []ssh.AuthMethod
|
auth []ssh.AuthMethod
|
||||||
hostKeys []ssh.PublicKey
|
|
||||||
conns map[string]conn
|
conns map[string]conn
|
||||||
rw sync.RWMutex
|
rw sync.RWMutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
@ -48,9 +45,6 @@ func New(ctx context.Context, name string, params config.DriverParams) (base.Dri
|
|||||||
if err := util.UnmarshalParams(params, &drv.params); err != nil {
|
if err := util.UnmarshalParams(params, &drv.params); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := drv.buildHostKeys(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
matcher, err := makeDomainCatchRegExp(drv.params.DomainExtractRegex)
|
matcher, err := makeDomainCatchRegExp(drv.params.DomainExtractRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid domain_extract_regex: %w", err)
|
return nil, fmt.Errorf("invalid domain_extract_regex: %w", err)
|
||||||
@ -64,12 +58,10 @@ func New(ctx context.Context, name string, params config.DriverParams) (base.Dri
|
|||||||
func (d *SSH) forward(val sshtun.Forward, domainMatcher func(string)) conn {
|
func (d *SSH) forward(val sshtun.Forward, domainMatcher func(string)) conn {
|
||||||
tun := sshtun.New(d.params.Address,
|
tun := sshtun.New(d.params.Address,
|
||||||
d.params.Auth.User,
|
d.params.Auth.User,
|
||||||
|
val,
|
||||||
d.auth,
|
d.auth,
|
||||||
sshtun.TunnelConfig{
|
sshtun.SessionConfig{
|
||||||
Forward: val,
|
|
||||||
HostKeys: d.hostKeys,
|
|
||||||
NoPTY: d.params.NoPTY,
|
NoPTY: d.params.NoPTY,
|
||||||
Shell: d.params.Shell,
|
|
||||||
FakeRemoteHost: d.params.FakeRemoteHost,
|
FakeRemoteHost: d.params.FakeRemoteHost,
|
||||||
KeepAliveInterval: uint(d.params.KeepAlive.Interval),
|
KeepAliveInterval: uint(d.params.KeepAlive.Interval),
|
||||||
KeepAliveMax: uint(d.params.KeepAlive.MaxAttempts),
|
KeepAliveMax: uint(d.params.KeepAlive.MaxAttempts),
|
||||||
@ -88,46 +80,6 @@ func (d *SSH) forward(val sshtun.Forward, domainMatcher func(string)) conn {
|
|||||||
return conn{ctx: ctx, cancel: cancel, tun: tun}
|
return conn{ctx: ctx, cancel: cancel, tun: tun}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSH) buildHostKeys() error {
|
|
||||||
if d.params.HostKeys == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
hostKeys := []ssh.PublicKey{}
|
|
||||||
for _, keyLine := range strings.Split(d.params.HostKeys, "\n") {
|
|
||||||
key, err := d.pubKeyFromSSHKeyScan(keyLine)
|
|
||||||
if err != nil {
|
|
||||||
d.Log().Debugf("invalid public key: %s", keyLine)
|
|
||||||
return fmt.Errorf("invalid public key for the host: %w", err)
|
|
||||||
}
|
|
||||||
if key != nil {
|
|
||||||
hostKeys = append(hostKeys, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.hostKeys = hostKeys
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pubKeyFromSSHKeyScan extracts host public key from ssh-keyscan output format.
|
|
||||||
func (d *SSH) pubKeyFromSSHKeyScan(line string) (key ssh.PublicKey, err error) {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "#") || line == "" { // comment or empty line - should be ignored.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
cols := strings.Fields(line)
|
|
||||||
for i := len(cols) - 1; i >= 0; i-- {
|
|
||||||
col := strings.TrimSpace(cols[i])
|
|
||||||
keyData, err := base64.StdEncoding.DecodeString(col)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, err = ssh.ParsePublicKey(keyData)
|
|
||||||
if err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("no public key in the provided data")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSH) makeDomainMatcherFunc(containerID string) func(string) {
|
func (d *SSH) makeDomainMatcherFunc(containerID string) func(string) {
|
||||||
if d.domainRegExp == nil {
|
if d.domainRegExp == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -153,23 +105,13 @@ func (d *SSH) populateFromSSHConfig() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if user, err := cfg.Get(d.params.Address, "User"); err == nil && user != "" {
|
||||||
host := d.extractHostFromAddr(d.params.Address)
|
|
||||||
hostCfg := &hostConfig{cfg: cfg, host: host}
|
|
||||||
port, err := hostCfg.Get("Port")
|
|
||||||
if err != nil {
|
|
||||||
port = "22"
|
|
||||||
}
|
|
||||||
if hostName, err := hostCfg.Get("HostName"); err == nil && hostName != "" {
|
|
||||||
d.params.Address = net.JoinHostPort(hostName, port)
|
|
||||||
}
|
|
||||||
if user, err := hostCfg.Get("User"); err == nil && user != "" {
|
|
||||||
d.params.Auth.User = user
|
d.params.Auth.User = user
|
||||||
}
|
}
|
||||||
if usePass, err := hostCfg.Get("PasswordAuthentication"); err == nil && usePass == "yes" {
|
if usePass, err := cfg.Get(d.params.Address, "PasswordAuthentication"); err == nil && usePass == "yes" {
|
||||||
d.params.Auth.Type = types.AuthTypePassword
|
d.params.Auth.Type = types.AuthTypePassword
|
||||||
}
|
}
|
||||||
if keyfile, err := hostCfg.Get("IdentityFile"); err == nil && keyfile != "" {
|
if keyfile, err := cfg.Get(d.params.Address, "IdentityFile"); err == nil && keyfile != "" {
|
||||||
resolvedKeyFile, err := types.SmartPath(keyfile).Resolve(false)
|
resolvedKeyFile, err := types.SmartPath(keyfile).Resolve(false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d.params.Auth.Type = types.AuthTypeKey
|
d.params.Auth.Type = types.AuthTypeKey
|
||||||
@ -178,14 +120,6 @@ func (d *SSH) populateFromSSHConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SSH) extractHostFromAddr(addr string) string {
|
|
||||||
host, _, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SSH) Handle(event dto.Event) error {
|
func (d *SSH) Handle(event dto.Event) error {
|
||||||
defer d.rw.Unlock()
|
defer d.rw.Unlock()
|
||||||
d.rw.Lock()
|
d.rw.Lock()
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
type Params struct {
|
type Params struct {
|
||||||
Address string `mapstructure:"address" validate:"required"`
|
Address string `mapstructure:"address" validate:"required"`
|
||||||
HostKeys string `mapstructure:"host_keys"`
|
|
||||||
DefaultHost *string `mapstructure:"default_host,omitempty"`
|
DefaultHost *string `mapstructure:"default_host,omitempty"`
|
||||||
ForwardPort uint16 `mapstructure:"forward_port"`
|
ForwardPort uint16 `mapstructure:"forward_port"`
|
||||||
Auth types.Auth `mapstructure:"auth"`
|
Auth types.Auth `mapstructure:"auth"`
|
||||||
@ -18,7 +17,6 @@ type Params struct {
|
|||||||
Mode types.DomainMode `mapstructure:"mode" validate:"required,oneof=single multi"`
|
Mode types.DomainMode `mapstructure:"mode" validate:"required,oneof=single multi"`
|
||||||
FakeRemoteHost bool `mapstructure:"fake_remote_host"`
|
FakeRemoteHost bool `mapstructure:"fake_remote_host"`
|
||||||
NoPTY bool `mapstructure:"nopty"`
|
NoPTY bool `mapstructure:"nopty"`
|
||||||
Shell bool `mapstructure:"shell"`
|
|
||||||
Commands types.Commands `mapstructure:"commands"`
|
Commands types.Commands `mapstructure:"commands"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,22 +3,11 @@ package ssh
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/internal/server/driver/ssh/types"
|
"github.com/Neur0toxine/sshpoke/internal/server/driver/ssh/types"
|
||||||
"github.com/kevinburke/ssh_config"
|
"github.com/kevinburke/ssh_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hostConfig struct {
|
|
||||||
cfg *ssh_config.Config
|
|
||||||
host string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hostConfig) Get(key string) (string, error) {
|
|
||||||
val, err := c.cfg.Get(c.host, key)
|
|
||||||
return strings.TrimSpace(val), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSSHConfig(filePath types.SmartPath) (*ssh_config.Config, error) {
|
func parseSSHConfig(filePath types.SmartPath) (*ssh_config.Config, error) {
|
||||||
fileName, err := filePath.Resolve(false)
|
fileName, err := filePath.Resolve(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -19,28 +19,27 @@ var NoopSessionCallback SessionCallback = func(*ssh.Session) {}
|
|||||||
type Tunnel struct {
|
type Tunnel struct {
|
||||||
user string
|
user string
|
||||||
address Endpoint
|
address Endpoint
|
||||||
|
forward Forward
|
||||||
authMethods []ssh.AuthMethod
|
authMethods []ssh.AuthMethod
|
||||||
log *zap.SugaredLogger
|
log *zap.SugaredLogger
|
||||||
tunConfig TunnelConfig
|
sessConfig SessionConfig
|
||||||
connected atomic.Bool
|
connected atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunnelConfig struct {
|
type SessionConfig struct {
|
||||||
Forward Forward
|
|
||||||
HostKeys []ssh.PublicKey
|
|
||||||
NoPTY bool
|
NoPTY bool
|
||||||
Shell bool
|
|
||||||
FakeRemoteHost bool
|
FakeRemoteHost bool
|
||||||
KeepAliveInterval uint
|
KeepAliveInterval uint
|
||||||
KeepAliveMax uint
|
KeepAliveMax uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(address, user string, auth []ssh.AuthMethod, sc TunnelConfig, log *zap.SugaredLogger) *Tunnel {
|
func New(address, user string, forward Forward, auth []ssh.AuthMethod, sc SessionConfig, log *zap.SugaredLogger) *Tunnel {
|
||||||
return &Tunnel{
|
return &Tunnel{
|
||||||
address: AddrToEndpoint(address),
|
address: AddrToEndpoint(address),
|
||||||
user: user,
|
user: user,
|
||||||
|
forward: forward,
|
||||||
authMethods: auth,
|
authMethods: auth,
|
||||||
tunConfig: sc,
|
sessConfig: sc,
|
||||||
log: log.With(zap.String("sshServer", address)),
|
log: log.With(zap.String("sshServer", address)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,16 +68,6 @@ func (t *Tunnel) Connect(ctx context.Context, bannerCb ssh.BannerCallback, sessi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) buildHostKeyCallback() ssh.HostKeyCallback {
|
|
||||||
if t.tunConfig.HostKeys == nil || len(t.tunConfig.HostKeys) == 0 {
|
|
||||||
return ssh.InsecureIgnoreHostKey()
|
|
||||||
}
|
|
||||||
if len(t.tunConfig.HostKeys) == 1 {
|
|
||||||
return ssh.FixedHostKey(t.tunConfig.HostKeys[0])
|
|
||||||
}
|
|
||||||
return FixedHostKeys(t.tunConfig.HostKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect once to the SSH server. if the connection breaks, we return error and the caller
|
// connect once to the SSH server. if the connection breaks, we return error and the caller
|
||||||
// will try to re-connect
|
// will try to re-connect
|
||||||
func (t *Tunnel) connect(ctx context.Context, bannerCb ssh.BannerCallback, sessionCb SessionCallback) error {
|
func (t *Tunnel) connect(ctx context.Context, bannerCb ssh.BannerCallback, sessionCb SessionCallback) error {
|
||||||
@ -86,7 +75,7 @@ func (t *Tunnel) connect(ctx context.Context, bannerCb ssh.BannerCallback, sessi
|
|||||||
sshConfig := &ssh.ClientConfig{
|
sshConfig := &ssh.ClientConfig{
|
||||||
User: t.user,
|
User: t.user,
|
||||||
Auth: t.authMethods,
|
Auth: t.authMethods,
|
||||||
HostKeyCallback: t.buildHostKeyCallback(),
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
BannerCallback: bannerCb,
|
BannerCallback: bannerCb,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,25 +105,19 @@ func (t *Tunnel) connect(ctx context.Context, bannerCb ssh.BannerCallback, sessi
|
|||||||
sessionCb = func(*ssh.Session) {}
|
sessionCb = func(*ssh.Session) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !t.tunConfig.NoPTY {
|
if !t.sessConfig.NoPTY {
|
||||||
err = sess.RequestPty("xterm", 80, 40, ssh.TerminalModes{
|
err = sess.RequestPty("xterm", 80, 40, ssh.TerminalModes{
|
||||||
ssh.ECHO: 0,
|
ssh.ECHO: 0,
|
||||||
ssh.IGNCR: 1,
|
ssh.IGNCR: 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Warnf("PTY allocation failed: %s", err.Error())
|
t.log.Warnf("PTY allocation failed: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
if err := sess.Shell(); err != nil {
|
||||||
|
t.log.Warnf("failed to start shell: %s", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.tunConfig.Shell {
|
|
||||||
if err := sess.Shell(); err != nil {
|
|
||||||
t.log.Warnf("failed to start shell: %s", err.Error())
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
_ = sess.Wait()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package sshtun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FixedHostKeys(keys []ssh.PublicKey) ssh.HostKeyCallback {
|
|
||||||
m := make(map[string]ssh.PublicKey)
|
|
||||||
for _, key := range keys {
|
|
||||||
m[key.Type()] = key
|
|
||||||
}
|
|
||||||
hk := &fixedHostKeys{keys: m}
|
|
||||||
return hk.check
|
|
||||||
}
|
|
||||||
|
|
||||||
type fixedHostKeys struct {
|
|
||||||
keys map[string]ssh.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fixedHostKeys) check(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
||||||
if f.keys == nil {
|
|
||||||
return fmt.Errorf("ssh: host keys should be defined")
|
|
||||||
}
|
|
||||||
if len(f.keys) == 0 {
|
|
||||||
return fmt.Errorf("ssh: no host keys were provided")
|
|
||||||
}
|
|
||||||
hostKey, found := f.keys[key.Type()]
|
|
||||||
if !found || !bytes.Equal(key.Marshal(), hostKey.Marshal()) {
|
|
||||||
return fmt.Errorf("ssh: host key mismatch")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -53,13 +53,13 @@ func (t *Tunnel) reverseForwardOnePort(sshClient *ssh.Client, listenerStopped ch
|
|||||||
listener net.Listener
|
listener net.Listener
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if t.tunConfig.FakeRemoteHost {
|
if t.sessConfig.FakeRemoteHost {
|
||||||
listener, err = sshClient.ListenTCP(&net.TCPAddr{
|
listener, err = sshClient.ListenTCP(&net.TCPAddr{
|
||||||
IP: t.ipFromAddr(sshClient.Conn.RemoteAddr()),
|
IP: t.ipFromAddr(sshClient.Conn.RemoteAddr()),
|
||||||
Port: t.tunConfig.Forward.Remote.Port,
|
Port: t.forward.Remote.Port,
|
||||||
}, t.tunConfig.Forward.Remote.Host)
|
}, t.forward.Remote.Host)
|
||||||
} else {
|
} else {
|
||||||
listener, err = sshClient.Listen("tcp", t.tunConfig.Forward.Remote.String())
|
listener, err = sshClient.Listen("tcp", t.forward.Remote.String())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -67,8 +67,7 @@ func (t *Tunnel) reverseForwardOnePort(sshClient *ssh.Client, listenerStopped ch
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
t.log.Debugf("forwarding %s <- %s",
|
t.log.Debugf("forwarding %s <- %s", t.forward.Local.String(), t.forward.Remote.String())
|
||||||
t.tunConfig.Forward.Local.String(), t.tunConfig.Forward.Remote.String())
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
client, err := listener.Accept()
|
client, err := listener.Accept()
|
||||||
@ -77,7 +76,7 @@ func (t *Tunnel) reverseForwardOnePort(sshClient *ssh.Client, listenerStopped ch
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go handleReverseForwardConn(client, t.tunConfig.Forward, t.log)
|
go handleReverseForwardConn(client, t.forward, t.log)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
// assume that the underlying net.Conn abruptly died.
|
// assume that the underlying net.Conn abruptly died.
|
||||||
func (t *Tunnel) keepAlive(ctx context.Context, client *ssh.Client, wg *sync.WaitGroup) {
|
func (t *Tunnel) keepAlive(ctx context.Context, client *ssh.Client, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if t.tunConfig.KeepAliveInterval == 0 || t.tunConfig.KeepAliveMax == 0 {
|
if t.sessConfig.KeepAliveInterval == 0 || t.sessConfig.KeepAliveMax == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ func (t *Tunnel) keepAlive(ctx context.Context, client *ssh.Client, wg *sync.Wai
|
|||||||
|
|
||||||
// Repeatedly check if the remote server is still alive.
|
// Repeatedly check if the remote server is still alive.
|
||||||
var aliveCount int32
|
var aliveCount int32
|
||||||
ticker := time.NewTicker(time.Duration(t.tunConfig.KeepAliveInterval) * time.Second)
|
ticker := time.NewTicker(time.Duration(t.sessConfig.KeepAliveInterval) * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -43,7 +43,7 @@ func (t *Tunnel) keepAlive(ctx context.Context, client *ssh.Client, wg *sync.Wai
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if n := atomic.AddInt32(&aliveCount, 1); n > int32(t.tunConfig.KeepAliveMax) {
|
if n := atomic.AddInt32(&aliveCount, 1); n > int32(t.sessConfig.KeepAliveMax) {
|
||||||
t.log.Error("keep-alive failed, closing connection...")
|
t.log.Error("keep-alive failed, closing connection...")
|
||||||
_ = client.Close()
|
_ = client.Close()
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user