/
proxyserver.go
158 lines (122 loc) · 4.3 KB
/
proxyserver.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
package ssh
import (
"bytes"
"errors"
"fmt"
"net"
"regexp"
"strings"
"time"
"github.com/notion/bastion/config"
"golang.org/x/crypto/ssh"
)
func startProxyServer(addr string, env *config.Env) {
signer := ParsePrivateKey(env.Config.PrivateKey, env.PKPassphrase, env)
sshConfig := getSSHProxyConfig(env)
sshConfig.AddHostKey(signer)
env.Blue.Println("Added RSA Keypair to SSH Server")
listener, err := net.Listen("tcp", addr)
if err != nil {
env.Red.Fatal(err)
}
defer listener.Close()
env.Green.Println("Running SSH proxy server at:", addr)
for {
tcpConn, err := listener.Accept()
if err != nil {
env.Red.Printf("Failed to accept incoming connection (%s)", err)
continue
}
SSHConn := &ProxyHandler{Conn: tcpConn, config: sshConfig, env: env}
go func() {
SSHConn.Serve()
env.SSHProxyClients.Delete(tcpConn.RemoteAddr().String())
env.WebsocketClients.Delete(tcpConn.RemoteAddr().String())
}()
env.Yellow.Printf("New connection from %s", tcpConn.RemoteAddr())
}
}
func getSSHProxyConfig(env *config.Env) *ssh.ServerConfig {
serverSigner := ParsePrivateKey(env.Config.ServerPrivateKey, env.PKPassphrase, env)
return &ssh.ServerConfig{
NoClientAuth: false,
PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
env.Yellow.Printf("Login attempt: %s, user %s key: %s", c.RemoteAddr(), c.User(), key)
if proxClient, ok := env.SSHProxyClients.Load(c.RemoteAddr().String()); ok {
proxyClient := proxClient.(*config.SSHProxyClient)
if len(proxyClient.SSHServerClient.Errors) > 0 {
return nil, nil
}
duration := time.Minute * 1
casigner := NewCASigner(serverSigner, duration, []string{}, []string{})
cert, PK, err := casigner.Sign(env, "root", nil)
if err != nil {
env.Red.Println("Unable to sign PK:", err)
}
newSigner := ParsePrivateKey(PK, env.PKPassphrase, env)
certsigner, err := ssh.NewCertSigner(cert, newSigner)
if err != nil {
env.Red.Println("Error loading cert signer:", err)
}
clientConfig := &ssh.ClientConfig{
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
User: "root",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(certsigner),
},
Timeout: 2 * time.Second,
}
rawProxyConn, err := net.DialTimeout("tcp", proxyClient.SSHServerClient.ProxyTo, clientConfig.Timeout)
if err != nil {
proxyClient.SSHServerClient.Errors = append(proxyClient.SSHServerClient.Errors, fmt.Errorf("Error in proxy authentication: %s", err))
env.Red.Println("Error in proxy authentication:", err)
return nil, nil
}
proxyConn, proxyChans, proxyReqs, err := ssh.NewClientConn(rawProxyConn, proxyClient.SSHServerClient.ProxyTo, clientConfig)
if err != nil {
proxyClient.SSHServerClient.Errors = append(proxyClient.SSHServerClient.Errors, fmt.Errorf("Error in proxy authentication: %s", err))
env.Red.Println("Error in proxy authentication:", err)
return nil, nil
}
fakeChan := make(chan ssh.NewChannel, 0)
fakeReqs := make(chan *ssh.Request, 0)
client := ssh.NewClient(proxyConn, fakeChan, fakeReqs)
close(fakeChan)
close(fakeReqs)
session, _ := client.NewSession()
defer session.Close()
var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Run("hostname")
proxyClient.SSHServerClient.ProxyToHostname = strings.TrimSpace(stdoutBuf.String())
authed := false
for _, v := range GetRegexMatches(proxyClient.SSHServerClient.User) {
if v == "" {
continue
}
regexMatch, err := regexp.MatchString(v, proxyClient.SSHServerClient.ProxyToHostname)
if err != nil {
env.Red.Println("Unable to match regex for host:", err)
break
}
if regexMatch {
authed = true
}
}
if !authed {
defer client.Close()
proxyClient.SSHServerClient.Errors = append(proxyClient.SSHServerClient.Errors, fmt.Errorf("You are not authorized to login to host: %s", proxyClient.SSHServerClient.ProxyToHostname))
return nil, nil
}
proxyClient.SSHClient = client
proxyClient.SSHConn = proxyConn
proxyClient.SSHClientChans = proxyChans
proxyClient.SSHClientReqs = proxyReqs
return nil, err
}
return nil, errors.New("can't find initial proxy connection")
},
}
}