-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathssh_tunnel.go
203 lines (170 loc) · 5.28 KB
/
ssh_tunnel.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package waygate
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"github.com/caddyserver/certmagic"
"golang.org/x/crypto/ssh"
//"go.uber.org/zap"
)
type acceptResult struct {
tlsConn *tls.Conn
err error
}
// Listener that owns an SSH client, which is closed when the listener closes.
type sshListener struct {
client *ssh.Client
listener net.Listener
acceptChan chan acceptResult
}
func NewSshListener(client *ssh.Client, listener net.Listener) *sshListener {
// Use random unprivileged port for ACME challenges. This is necessary
// because of the way certmagic works, in that if it fails to bind
// HTTPSPort (443 by default) and doesn't detect anything else binding
// it, it fails. Obviously the boringproxy client is likely to be
// running on a machine where 443 isn't bound, so we need a different
// port to hack around this. See here for more details:
// https://github.com/caddyserver/certmagic/issues/111
//var err error
//certmagic.HTTPSPort, err = randomOpenPort()
//if err != nil {
// return errors.New("Failed get random port for TLS challenges")
//}
certmagic.DefaultACME.DisableHTTPChallenge = true
//certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
certmagic.Default.OnDemand = &certmagic.OnDemandConfig{
DecisionFunc: func(name string) error {
return nil
},
}
//logger, _ := zap.NewDevelopment()
//certmagic.Default.Logger = logger
certConfig := certmagic.NewDefault()
tlsConfig := &tls.Config{
GetCertificate: certConfig.GetCertificate,
}
tlsConfig.NextProtos = append([]string{"http/1.1", "h2", "acme-tls/1"}, tlsConfig.NextProtos...)
acceptChan := make(chan acceptResult)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
acceptChan <- acceptResult{
tlsConn: nil,
err: err,
}
break
}
tlsConn := tls.Server(conn, tlsConfig)
go func(innerTlsConn *tls.Conn) {
innerTlsConn.Handshake()
if innerTlsConn.ConnectionState().NegotiatedProtocol == "acme-tls/1" {
innerTlsConn.Close()
} else {
acceptChan <- acceptResult{
tlsConn: innerTlsConn,
err: nil,
}
}
}(tlsConn)
}
}()
return &sshListener{
client: client,
listener: listener,
acceptChan: acceptChan,
}
}
func (l *sshListener) Accept() (net.Conn, error) {
result := <-l.acceptChan
return result.tlsConn, result.err
}
func (l *sshListener) Addr() net.Addr {
return l.listener.Addr()
}
func (l *sshListener) Close() error {
err := l.client.Close()
if err != nil {
return err
}
return l.listener.Close()
}
func MakeSshListener(tunnel SSHTunnel) (net.Listener, error) {
signer, err := ssh.ParsePrivateKey([]byte(tunnel.ClientPrivateKey))
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to parse private key: %v", err))
}
//var hostKey ssh.PublicKey
config := &ssh.ClientConfig{
User: tunnel.Username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
//HostKeyCallback: ssh.FixedHostKey(hostKey),
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
sshHost := fmt.Sprintf("%s:%d", tunnel.ServerAddress, tunnel.ServerPort)
client, err := ssh.Dial("tcp", sshHost, config)
if err != nil {
return nil, errors.New(fmt.Sprintf("Failed to dial: ", err))
}
//defer client.Close()
bindAddr := "127.0.0.1"
tunnelAddr := fmt.Sprintf("%s:%d", bindAddr, tunnel.ServerTunnelPort)
listener, err := client.Listen("tcp", tunnelAddr)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to register tcp forward for %s:%d %v", bindAddr, tunnel.ServerTunnelPort, err))
}
l := NewSshListener(client, listener)
return l, nil
}
func BoreSshTunnel(ctx context.Context, tunnel SSHTunnel, localPort int) error {
// Use random unprivileged port for ACME challenges. This is necessary
// because of the way certmagic works, in that if it fails to bind
// HTTPSPort (443 by default) and doesn't detect anything else binding
// it, it fails. Obviously the boringproxy client is likely to be
// running on a machine where 443 isn't bound, so we need a different
// port to hack around this. See here for more details:
// https://github.com/caddyserver/certmagic/issues/111
var err error
certmagic.HTTPSPort, err = randomOpenPort()
if err != nil {
return errors.New("Failed get random port for TLS challenges")
}
certmagic.DefaultACME.DisableHTTPChallenge = true
//certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
certConfig := certmagic.NewDefault()
listener, err := MakeSshListener(tunnel)
if err != nil {
return errors.New(fmt.Sprintf("Unable to register tcp forward %v", err))
}
defer listener.Close()
go func() {
for {
conn, err := listener.Accept()
if err != nil {
// TODO: Currently assuming an error means the
// tunnel was manually deleted, but there
// could be other errors that we should be
// attempting to recover from rather than
// breaking.
break
//continue
}
unwrapTls := true
go ProxyTcp(conn, "localhost", localPort, unwrapTls, certConfig)
}
}()
// TODO: There's still quite a bit of duplication with what the server does. Could we
// encapsulate it into a type?
err = certConfig.ManageSync(ctx, []string{tunnel.Domains[0]})
if err != nil {
fmt.Println("CertMagic error at startup")
fmt.Println(err)
}
fmt.Println("Tunnel opened on port", localPort)
<-ctx.Done()
return nil
}