Add port forwarding (-forward flag) (#7)
This commit is contained in:
parent
efff2172ee
commit
1a4768ba69
@ -27,6 +27,10 @@ var (
|
||||
hostKeyPath = flag.String("host_key",
|
||||
"/perm/breakglass.host_key",
|
||||
"path to a PEM-encoded RSA, DSA or ECDSA private key (create using e.g. ssh-keygen -f /perm/breakglass.host_key -N '' -t rsa)")
|
||||
|
||||
forwarding = flag.String("forward",
|
||||
"",
|
||||
"allow port forwarding. Use `loopback` for loopback interfaces and `private-network` for private networks")
|
||||
)
|
||||
|
||||
func loadAuthorizedKeys(path string) (map[string]bool, error) {
|
||||
|
6
go.sum
6
go.sum
@ -2,9 +2,6 @@ github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/gokrazy/gokrazy v0.0.0-20190321081644-520b8ca41de7 h1:lpARmqp1bhgN6Co5NJpTwIxQoiz3L1TLzuJqsueDlbY=
|
||||
github.com/gokrazy/gokrazy v0.0.0-20190321081644-520b8ca41de7/go.mod h1:YbpshsItGhDXnytFAvMTRvZvGkVSpZV/4mwxQsvqzHg=
|
||||
github.com/gokrazy/internal v0.0.0-20190630091051-de21a662e434 h1:3NgMIyCbCOWhjO/9/yIloXsQCuP6MLolya2SItd1NCM=
|
||||
github.com/gokrazy/internal v0.0.0-20190630091051-de21a662e434/go.mod h1:c7C8E8dlEJG/vdLtGN5NJPdbKNzZi/puMD0sKC346TI=
|
||||
github.com/gokrazy/internal v0.0.0-20200527063429-7e4057347a0f h1:7XojstPtQ87IJ9VZbFuKP7tFQDo1DXZU9WNx6IHS+5A=
|
||||
github.com/gokrazy/internal v0.0.0-20200527163528-2cb9182fefff h1:NKE8iZv7t84+Tv29zmSe5FdjlQY1P8CX1D2PbTQTvBY=
|
||||
github.com/gokrazy/internal v0.0.0-20200527163528-2cb9182fefff/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
|
||||
@ -12,13 +9,12 @@ github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIE
|
||||
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0=
|
||||
golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
95
ssh.go
95
ssh.go
@ -6,24 +6,113 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gokrazy/gokrazy"
|
||||
"github.com/google/shlex"
|
||||
"github.com/kr/pty"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func handleChannel(newChannel ssh.NewChannel) {
|
||||
if t := newChannel.ChannelType(); t != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %q", t))
|
||||
func handleChannel(newChan ssh.NewChannel) {
|
||||
switch t := newChan.ChannelType(); t {
|
||||
case "session":
|
||||
handleSession(newChan)
|
||||
case "direct-tcpip":
|
||||
handleTCPIP(newChan)
|
||||
default:
|
||||
newChan.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %q", t))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func parseAddr(addr string) net.IP {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
if ips, err := net.LookupIP(addr); err == nil {
|
||||
ip = ips[0] // use first address found
|
||||
}
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
// Forwarding ported from https://github.com/gliderlabs/ssh (BSD3 License)
|
||||
|
||||
// direct-tcpip data struct as specified in RFC4254, Section 7.2
|
||||
type localForwardChannelData struct {
|
||||
DestAddr string
|
||||
DestPort uint32
|
||||
|
||||
OriginAddr string
|
||||
OriginPort uint32
|
||||
}
|
||||
|
||||
func handleTCPIP(newChan ssh.NewChannel) {
|
||||
d := localForwardChannelData{}
|
||||
if err := ssh.Unmarshal(newChan.ExtraData(), &d); err != nil {
|
||||
newChan.Reject(ssh.ConnectionFailed, "error parsing forward data: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var ip net.IP
|
||||
switch *forwarding {
|
||||
case "loopback":
|
||||
if ip = parseAddr(d.DestAddr); ip != nil && !ip.IsLoopback() {
|
||||
newChan.Reject(ssh.Prohibited, "port forwarding not allowed for address")
|
||||
return
|
||||
}
|
||||
case "private-network":
|
||||
if ip = parseAddr(d.DestAddr); ip != nil && !gokrazy.IsInPrivateNet(ip) {
|
||||
newChan.Reject(ssh.Prohibited, "port forwarding not allowed for address")
|
||||
return
|
||||
}
|
||||
default:
|
||||
newChan.Reject(ssh.Prohibited, "port forwarding is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
// fallthrough for forwarding enabled, validate ip != nil once
|
||||
if ip == nil {
|
||||
newChan.Reject(ssh.Prohibited, "host not reachable")
|
||||
}
|
||||
|
||||
dest := net.JoinHostPort(ip.String(), strconv.Itoa(int(d.DestPort)))
|
||||
|
||||
var dialer net.Dialer
|
||||
dconn, err := dialer.DialContext(context.Background(), "tcp", dest)
|
||||
if err != nil {
|
||||
newChan.Reject(ssh.ConnectionFailed, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ch, reqs, err := newChan.Accept()
|
||||
if err != nil {
|
||||
dconn.Close()
|
||||
return
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
go func() {
|
||||
defer ch.Close()
|
||||
defer dconn.Close()
|
||||
io.Copy(ch, dconn)
|
||||
}()
|
||||
go func() {
|
||||
defer ch.Close()
|
||||
defer dconn.Close()
|
||||
io.Copy(dconn, ch)
|
||||
}()
|
||||
}
|
||||
|
||||
func handleSession(newChannel ssh.NewChannel) {
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Printf("Could not accept channel (%s)", err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user