diff --git a/go.mod b/go.mod index 2d8a88a..42d2fa1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/gofrs/uuid v3.2.0+incompatible github.com/hpcloud/tail v1.0.0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c + github.com/kevinburke/ssh_config v1.1.0 github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/pelletier/go-buffruneio v0.2.0 // indirect diff --git a/go.sum b/go.sum index 9db72fb..9677592 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o= +github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= diff --git a/tunnel/config.go b/tunnel/config.go index 963e124..938e65a 100644 --- a/tunnel/config.go +++ b/tunnel/config.go @@ -66,12 +66,12 @@ func (r SSHConfigFile) Get(host string) *SSHHost { user = "" } - localForward, err := r.getForward("LocalForward", host) + localForwards, err := r.getForwards("LocalForward", host) if err != nil { log.Warningf("error reading local forwarding configuration from ssh config file: %v", err) } - remoteForward, err := r.getForward("RemoteForward", host) + remoteForwards, err := r.getForwards("RemoteForward", host) if err != nil { log.Warningf("error reading remote configuration from ssh config file: %v", err) } @@ -84,13 +84,13 @@ func (r SSHConfigFile) Get(host string) *SSHHost { } return &SSHHost{ - Hostname: hostname, - Port: port, - User: user, - Key: key, - IdentityAgent: identityAgent, - LocalForward: localForward, - RemoteForward: remoteForward, + Hostname: hostname, + Port: port, + User: user, + Key: key, + IdentityAgent: identityAgent, + LocalForwards: localForwards, + RemoteForwards: remoteForwards, } } @@ -103,34 +103,44 @@ func (r SSHConfigFile) getHostname(host string) string { return hostname } -func (r SSHConfigFile) getForward(forwardType, host string) (*ForwardConfig, error) { - c, err := r.sshConfig.Get(host, forwardType) +func (r SSHConfigFile) getForwards(forwardType, host string) ([]*ForwardConfig, error) { + fwds, err := r.sshConfig.GetAll(host, forwardType) if err != nil { return nil, err } - if c == "" { - return nil, nil - } + forwards := []*ForwardConfig{} + + for _, c := range fwds { + if c == "" { + continue + } - l := strings.Fields(c) + l := strings.Fields(c) - if len(l) < 2 { - return nil, fmt.Errorf("malformed forwarding configuration on ssh config file: %s", l) - } + if len(l) < 2 { + return nil, fmt.Errorf("malformed forwarding configuration on ssh config file: %s", l) + } - source := l[0] - destination := l[1] + source := l[0] + destination := l[1] - if strings.HasPrefix(source, ":") { - source = fmt.Sprintf("127.0.0.1%s", source) + if strings.HasPrefix(source, ":") { + source = fmt.Sprintf("127.0.0.1%s", source) + } + + if source != "" && !strings.Contains(source, ":") { + source = fmt.Sprintf("127.0.0.1:%s", source) + } + + forwards = append(forwards, &ForwardConfig{Source: source, Destination: destination}) } - if source != "" && !strings.Contains(source, ":") { - source = fmt.Sprintf("127.0.0.1:%s", source) + if len(forwards) == 0 { + return nil, nil } - return &ForwardConfig{Source: source, Destination: destination}, nil + return forwards, nil } @@ -154,18 +164,18 @@ func (r SSHConfigFile) getKey(host string) string { // SSHHost represents a host configuration extracted from a ssh config file. type SSHHost struct { - Hostname string - Port string - User string - Key string - IdentityAgent string - LocalForward *ForwardConfig - RemoteForward *ForwardConfig + Hostname string + Port string + User string + Key string + IdentityAgent string + LocalForwards []*ForwardConfig + RemoteForwards []*ForwardConfig } // String returns a string representation of a SSHHost. func (h SSHHost) String() string { - return fmt.Sprintf("[hostname=%s, port=%s, user=%s, key=%s, identity_agent=%s, local_forward=%s, remote_forward=%s]", h.Hostname, h.Port, h.User, h.Key, h.IdentityAgent, h.LocalForward, h.RemoteForward) + return fmt.Sprintf("[hostname=%s, port=%s, user=%s, key=%s, identity_agent=%s, local_forward=%v, remote_forward=%v]", h.Hostname, h.Port, h.User, h.Key, h.IdentityAgent, h.LocalForwards, h.RemoteForwards) } // ForwardConfig represents either a LocalForward or a RemoteForward configuration diff --git a/tunnel/config_test.go b/tunnel/config_test.go index ccb0a00..eb83b92 100644 --- a/tunnel/config_test.go +++ b/tunnel/config_test.go @@ -20,11 +20,11 @@ Host example2 LocalForward 8080 127.0.0.1:8080 Host example3 LocalForward 9090 127.0.0.1:9090 + LocalForward 9091 127.0.0.1:9091 Host example4 RemoteForward 80 127.0.0.1:8080 Host example5 RemoteForward 192.168.1.100:80 my-server:8080 - ` c, _ := ssh_config.Decode(strings.NewReader(config)) @@ -37,51 +37,54 @@ Host example5 { "example1", &SSHHost{ - Hostname: "172.17.0.1", - Port: "3306", - User: "john", - Key: "/path/.ssh/id_rsa", - LocalForward: nil, + Hostname: "172.17.0.1", + Port: "3306", + User: "john", + Key: "/path/.ssh/id_rsa", + LocalForwards: nil, }, }, { "example2", &SSHHost{ - Hostname: "", - Port: "", - User: "", - Key: "", - LocalForward: &ForwardConfig{Source: "127.0.0.1:8080", Destination: "127.0.0.1:8080"}, + Hostname: "", + Port: "", + User: "", + Key: "", + LocalForwards: []*ForwardConfig{&ForwardConfig{Source: "127.0.0.1:8080", Destination: "127.0.0.1:8080"}}, }, }, { "example3", &SSHHost{ - Hostname: "", - Port: "", - User: "", - Key: "", - LocalForward: &ForwardConfig{Source: "127.0.0.1:9090", Destination: "127.0.0.1:9090"}, + Hostname: "", + Port: "", + User: "", + Key: "", + LocalForwards: []*ForwardConfig{ + &ForwardConfig{Source: "127.0.0.1:9090", Destination: "127.0.0.1:9090"}, + &ForwardConfig{Source: "127.0.0.1:9091", Destination: "127.0.0.1:9091"}, + }, }, }, { "example4", &SSHHost{ - Hostname: "", - Port: "", - User: "", - Key: "", - RemoteForward: &ForwardConfig{Source: "127.0.0.1:80", Destination: "127.0.0.1:8080"}, + Hostname: "", + Port: "", + User: "", + Key: "", + RemoteForwards: []*ForwardConfig{&ForwardConfig{Source: "127.0.0.1:80", Destination: "127.0.0.1:8080"}}, }, }, { "example5", &SSHHost{ - Hostname: "", - Port: "", - User: "", - Key: "", - RemoteForward: &ForwardConfig{Source: "192.168.1.100:80", Destination: "my-server:8080"}, + Hostname: "", + Port: "", + User: "", + Key: "", + RemoteForwards: []*ForwardConfig{&ForwardConfig{Source: "192.168.1.100:80", Destination: "my-server:8080"}}, }, }, } diff --git a/tunnel/testdata/dotssh/config b/tunnel/testdata/dotssh/config index 3a9d0ab..bf41907 100644 --- a/tunnel/testdata/dotssh/config +++ b/tunnel/testdata/dotssh/config @@ -17,3 +17,10 @@ Host hostWithLocalForward User mole_test IdentityFile ~/.ssh/id_rsa +Host hostWithTwoLocalForwards + Hostname 127.0.0.1 + Port 2222 + LocalForward 8080 172.17.0.1:8080 + LocalForward 9090 172.17.0.1:9090 + User mole_test + IdentityFile ~/.ssh/id_rsa diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 103e1a5..91d428b 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -583,13 +583,17 @@ func buildSSHChannels(serverName, channelType string, source, destination []stri // if source and destination were not given, try to find the addresses from the // SSH configuration file. if len(source) == 0 && len(destination) == 0 { - f, err := getForward(channelType, serverName, cfgPath) + fwds, err := getForwards(channelType, serverName, cfgPath) if err != nil { return nil, err } - source = []string{f.Source} - destination = []string{f.Destination} + source = []string{} + destination = []string{} + for _, f := range fwds { + source = append(source, f.Source) + destination = append(destination, f.Destination) + } } else { lSize := len(source) @@ -640,8 +644,8 @@ func buildSSHChannels(serverName, channelType string, source, destination []stri return channels, nil } -func getForward(channelType, serverName string, cfgPath string) (*ForwardConfig, error) { - var f *ForwardConfig +func getForwards(channelType, serverName string, cfgPath string) ([]*ForwardConfig, error) { + var fwds []*ForwardConfig cfg, err := NewSSHConfigFile(cfgPath) if err != nil { @@ -651,16 +655,16 @@ func getForward(channelType, serverName string, cfgPath string) (*ForwardConfig, sh := cfg.Get(serverName) if channelType == "local" { - f = sh.LocalForward + fwds = sh.LocalForwards } else if channelType == "remote" { - f = sh.RemoteForward + fwds = sh.RemoteForwards } else { return nil, fmt.Errorf("could not retrieve forwarding information from ssh configuration file: unsupported channel type %s", channelType) } - if f == nil { + if fwds == nil { return nil, fmt.Errorf("forward config could not be found or has invalid syntax for host %s", serverName) } - return f, nil + return fwds, nil } diff --git a/tunnel/tunnel_test.go b/tunnel/tunnel_test.go index f65d225..9919f57 100644 --- a/tunnel/tunnel_test.go +++ b/tunnel/tunnel_test.go @@ -337,6 +337,14 @@ func TestBuildSSHChannels(t *testing.T) { expected: 1, expectedError: nil, }, + { + serverName: "hostWithTwoLocalForwards", + source: []string{}, + destination: []string{}, + config: "testdata/.ssh/config", + expected: 2, + expectedError: nil, + }, { serverName: "test", source: []string{":3360", ":8080"},