Skip to content
Permalink
Browse files

Merge pull request #26 from eko/added-forward-hostname

Added a new "forward_hostname" option for SSH port-forwarding
  • Loading branch information...
eko committed Oct 4, 2019
2 parents f59fdcb + 1ea0a53 commit fbfd5999e81e3cd1aa754f7c6dbcb361232876a5
Showing with 102 additions and 43 deletions.
  1. +3 −0 example.yaml
  2. +10 −9 pkg/config/model.go
  3. +4 −4 pkg/forwarder/forwarder.go
  4. +26 −19 pkg/forwarder/ssh/forwarder.go
  5. +59 −11 pkg/forwarder/ssh/forwarder_test.go
@@ -108,13 +108,16 @@ watcher: # Optional

# Example of SSH local forward: forward a specific remote port locally
# Here, I'm forwarding on port 8080 locally my production website (port 80)
# I am also pointing the SSH forward to the bastion.svc.local hostname so the SSH
# command will be: ssh -L 80:bastion.svc.local:8080 vincent@composieux.fr
<: &composieux-fr-local
name: composieux-fr-local
type: ssh
values:
remote: vincent@composieux.fr # SSH <user>@<hostname>
args:
- "-i/Users/name/.ssh/private_key"
forward_hostname: bastion.svc.local # Optional, SSH forward hostname. Defaults to 127.0.0.1
hostname: composieux.fr.svc.local # Optional
ports:
- 8080:80
@@ -92,15 +92,16 @@ func (f *Forward) IsProxified() bool {

// ForwardValues represents the available values for each forward type
type ForwardValues struct {
Context string `yaml:"context"`
Namespace string `yaml:"namespace"`
Labels map[string]string `yaml:"labels"`
Hostname string `yaml:"hostname"`
ProxyHostname string `yaml:"proxy_hostname"`
DisableProxy bool `yaml:"disable_proxy"`
Ports []string `yaml:"ports"`
Remote string `yaml:"remote"`
Args []string `yaml:"args"`
Context string `yaml:"context"`
Namespace string `yaml:"namespace"`
Labels map[string]string `yaml:"labels"`
ForwardHostname string `yaml:"forward_hostname"`
Hostname string `yaml:"hostname"`
ProxyHostname string `yaml:"proxy_hostname"`
DisableProxy bool `yaml:"disable_proxy"`
Ports []string `yaml:"ports"`
Remote string `yaml:"remote"`
Args []string `yaml:"args"`
}

// Watcher represents the configuration values for the file watcher component
@@ -166,9 +166,9 @@ func (f *Forwarder) forward(forward *config.Forward, wg *sync.WaitGroup) {
for _, proxyForward := range proxyForwards {
localPort, forwardPort := splitLocalAndForwardPorts(ports)
values.Remote = "root@127.0.0.1"
args := append(values.Args, fmt.Sprintf("-p %s", proxyForward.ProxyPort))
values.Args = append(values.Args, fmt.Sprintf("-p %s", proxyForward.ProxyPort))

forwarder, err := ssh.NewForwarder(f.view, config.ForwarderSSHRemote, values.Remote, localPort, forwardPort, args)
forwarder, err := ssh.NewForwarder(f.view, config.ForwarderSSHRemote, values, localPort, forwardPort)
if err != nil {
f.view.Writef("%s\n", err.Error())
return
@@ -181,7 +181,7 @@ func (f *Forwarder) forward(forward *config.Forward, wg *sync.WaitGroup) {
// SSH local forward: give proxy port as local port and forwarded port, use proxy
case config.ForwarderSSH:
for _, proxyForward := range proxyForwards {
forwarder, err := ssh.NewForwarder(f.view, forward.Type, values.Remote, proxyForward.ProxyPort, proxyForward.ForwardPort, values.Args)
forwarder, err := ssh.NewForwarder(f.view, forward.Type, values, proxyForward.ProxyPort, proxyForward.ForwardPort)
if err != nil {
f.view.Writef("%s\n", err.Error())
return
@@ -193,7 +193,7 @@ func (f *Forwarder) forward(forward *config.Forward, wg *sync.WaitGroup) {
// SSH remote forward: give local port and forwarded port, do not proxy
case config.ForwarderSSHRemote:
for _, proxyForward := range proxyForwards {
forwarder, err := ssh.NewForwarder(f.view, forward.Type, values.Remote, proxyForward.LocalPort, proxyForward.ForwardPort, values.Args)
forwarder, err := ssh.NewForwarder(f.view, forward.Type, values, proxyForward.LocalPort, proxyForward.ForwardPort)
if err != nil {
f.view.Writef("%s\n", err.Error())
return
@@ -10,31 +10,33 @@ import (
)

type Forwarder struct {
view ui.ViewInterface
forwardType string
remote string
localPort string
forwardPort string
args []string
cmd *exec.Cmd
stopChannel chan struct{}
readyChannel chan struct{}
view ui.ViewInterface
forwardType string
remote string
forwardHostname string
localPort string
forwardPort string
args []string
cmd *exec.Cmd
stopChannel chan struct{}
readyChannel chan struct{}
}

var (
execCommand = exec.Command
)

func NewForwarder(view ui.ViewInterface, forwardType, remote, localPort, forwardPort string, args []string) (*Forwarder, error) {
func NewForwarder(view ui.ViewInterface, forwardType string, values config.ForwardValues, localPort, forwardPort string) (*Forwarder, error) {
return &Forwarder{
view: view,
forwardType: forwardType,
remote: remote,
localPort: localPort,
forwardPort: forwardPort,
args: args,
stopChannel: make(chan struct{}),
readyChannel: make(chan struct{}, 1),
view: view,
forwardType: forwardType,
remote: values.Remote,
forwardHostname: values.ForwardHostname,
localPort: localPort,
forwardPort: forwardPort,
args: values.Args,
stopChannel: make(chan struct{}),
readyChannel: make(chan struct{}, 1),
}, nil
}

@@ -67,7 +69,12 @@ func (f *Forwarder) Forward() error {
forwardOption = "-R"
}

mapping := fmt.Sprintf("%s:127.0.0.1:%s", f.localPort, f.forwardPort)
var forwardHostname = "127.0.0.1" // Default SSH forward hostname if none specified in config
if f.forwardHostname != "" {
forwardHostname = f.forwardHostname
}

mapping := fmt.Sprintf("%s:%s:%s", f.localPort, forwardHostname, f.forwardPort)
host := f.remote

arguments := append([]string{
@@ -5,32 +5,35 @@ import (
"strings"
"testing"

"github.com/eko/monday/pkg/config"
uimocks "github.com/eko/monday/internal/tests/mocks/ui"
"github.com/eko/monday/pkg/config"
"github.com/stretchr/testify/assert"
)

func TestNewForwarder(t *testing.T) {
// Given
remote := "root@acme.tld"
localPort := "8080"
forwardPort := "8081"
args := []string{"-i /tmp/my/private.key"}

values := config.ForwardValues{
Remote: "root@acme.tld",
Args: []string{"-i /tmp/my/private.key"},
}

view := &uimocks.ViewInterface{}

// When
forwarder, err := NewForwarder(view, config.ForwarderSSH, remote, localPort, forwardPort, args)
forwarder, err := NewForwarder(view, config.ForwarderSSH, values, localPort, forwardPort)

// Then
assert.IsType(t, new(Forwarder), forwarder)
assert.Nil(t, err)

assert.Equal(t, config.ForwarderSSH, forwarder.forwardType)
assert.Equal(t, remote, forwarder.remote)
assert.Equal(t, values.Remote, forwarder.remote)
assert.Equal(t, localPort, forwarder.localPort)
assert.Equal(t, forwardPort, forwarder.forwardPort)
assert.Equal(t, args, forwarder.args)
assert.Equal(t, values.Args, forwarder.args)

assert.Nil(t, forwarder.cmd)
}
@@ -39,7 +42,11 @@ func TestGetForwardType(t *testing.T) {
// Given
view := &uimocks.ViewInterface{}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, "root@acme.tld", "8080", "8081", []string{})
values := config.ForwardValues{
Remote: "root@acme.tld",
}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, values, "8080", "8081")

// When
forwardType := forwarder.GetForwardType()
@@ -55,7 +62,11 @@ func TestGetReadyChannel(t *testing.T) {
// Given
view := &uimocks.ViewInterface{}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, "root@acme.tld", "8080", "8081", []string{})
values := config.ForwardValues{
Remote: "root@acme.tld",
}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, values, "8080", "8081")

// When
channel := forwarder.GetReadyChannel()
@@ -71,7 +82,11 @@ func TestGetStopChannel(t *testing.T) {
// Given
view := &uimocks.ViewInterface{}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, "root@acme.tld", "8080", "8081", []string{})
values := config.ForwardValues{
Remote: "root@acme.tld",
}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, values, "8080", "8081")

// When
channel := forwarder.GetStopChannel()
@@ -89,7 +104,36 @@ func TestForwardLocal(t *testing.T) {

view := &uimocks.ViewInterface{}

forwarder, err := NewForwarder(view, config.ForwarderSSH, "root@acme.tld", "8080", "8081", []string{})
values := config.ForwardValues{
Remote: "root@acme.tld",
ForwardHostname: "myforwardhostname.svc.local",
}

forwarder, err := NewForwarder(view, config.ForwarderSSH, values, "8080", "8081")

// When
err = forwarder.Forward()

// Then
assert.Nil(t, err)

assert.IsType(t, new(exec.Cmd), forwarder.cmd)

runCommand := strings.Replace(strings.Join(forwarder.cmd.Args, " "), "echo <ssh>", "ssh", -1)
assert.Equal(t, "ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -N -L 8080:myforwardhostname.svc.local:8081 root@acme.tld", runCommand)
}

func TestForwardLocalWithForwardHostname(t *testing.T) {
// Given
execCommand = mockExecCommand

view := &uimocks.ViewInterface{}

values := config.ForwardValues{
Remote: "root@acme.tld",
}

forwarder, err := NewForwarder(view, config.ForwarderSSH, values, "8080", "8081")

// When
err = forwarder.Forward()
@@ -109,7 +153,11 @@ func TestForwardRemote(t *testing.T) {

view := &uimocks.ViewInterface{}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, "root@acme.tld", "8080", "8081", []string{})
values := config.ForwardValues{
Remote: "root@acme.tld",
}

forwarder, err := NewForwarder(view, config.ForwarderSSHRemote, values, "8080", "8081")

// When
err = forwarder.Forward()

0 comments on commit fbfd599

Please sign in to comment.
You can’t perform that action at this time.