forked from jpillora/chisel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.go
244 lines (224 loc) · 6.1 KB
/
server.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package chserver
import (
"context"
"errors"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"regexp"
"time"
"github.com/gorilla/websocket"
chshare "github.com/jpillora/chisel/share"
"github.com/jpillora/chisel/share/ccrypto"
"github.com/jpillora/chisel/share/cio"
"github.com/jpillora/chisel/share/cnet"
"github.com/jpillora/chisel/share/settings"
"github.com/jpillora/requestlog"
"golang.org/x/crypto/ssh"
)
// Config is the configuration for the chisel service
type Config struct {
KeySeed string
KeyFile string
AuthFile string
Auth string
Proxy string
Socks5 bool
Reverse bool
KeepAlive time.Duration
TLS TLSConfig
}
// Server respresent a chisel service
type Server struct {
*cio.Logger
config *Config
fingerprint string
httpServer *cnet.HTTPServer
reverseProxy *httputil.ReverseProxy
sessCount int32
sessions *settings.Users
sshConfig *ssh.ServerConfig
users *settings.UserIndex
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0),
WriteBufferSize: settings.EnvInt("WS_BUFF_SIZE", 0),
}
// NewServer creates and returns a new chisel server
func NewServer(c *Config) (*Server, error) {
server := &Server{
config: c,
httpServer: cnet.NewHTTPServer(),
Logger: cio.NewLogger("server"),
sessions: settings.NewUsers(),
}
server.Info = true
server.users = settings.NewUserIndex(server.Logger)
if c.AuthFile != "" {
if err := server.users.LoadUsers(c.AuthFile); err != nil {
return nil, err
}
}
if c.Auth != "" {
u := &settings.User{Addrs: []*regexp.Regexp{settings.UserAllowAll}}
u.Name, u.Pass = settings.ParseAuth(c.Auth)
if u.Name != "" {
server.users.AddUser(u)
}
}
var pemBytes []byte
var err error
if c.KeyFile != "" {
var key []byte
if ccrypto.IsChiselKey([]byte(c.KeyFile)) {
key = []byte(c.KeyFile)
} else {
key, err = os.ReadFile(c.KeyFile)
if err != nil {
log.Fatalf("Failed to read key file %s", c.KeyFile)
}
}
pemBytes = key
if ccrypto.IsChiselKey(key) {
pemBytes, err = ccrypto.ChiselKey2PEM(key)
if err != nil {
log.Fatalf("Invalid key %s", string(key))
}
}
} else {
//generate private key (optionally using seed)
pemBytes, err = ccrypto.Seed2PEM(c.KeySeed)
if err != nil {
log.Fatal("Failed to generate key")
}
}
//convert into ssh.PrivateKey
private, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
log.Fatal("Failed to parse key")
}
//fingerprint this key
server.fingerprint = ccrypto.FingerprintKey(private.PublicKey())
//create ssh config
server.sshConfig = &ssh.ServerConfig{
ServerVersion: "SSH-" + chshare.ProtocolVersion + "-server",
PasswordCallback: server.authUser,
}
server.sshConfig.AddHostKey(private)
//setup reverse proxy
if c.Proxy != "" {
u, err := url.Parse(c.Proxy)
if err != nil {
return nil, err
}
if u.Host == "" {
return nil, server.Errorf("Missing protocol (%s)", u)
}
server.reverseProxy = httputil.NewSingleHostReverseProxy(u)
//always use proxy host
server.reverseProxy.Director = func(r *http.Request) {
//enforce origin, keep path
r.URL.Scheme = u.Scheme
r.URL.Host = u.Host
r.Host = u.Host
}
}
//print when reverse tunnelling is enabled
if c.Reverse {
server.Infof("Reverse tunnelling enabled")
}
return server, nil
}
// Run is responsible for starting the chisel service.
// Internally this calls Start then Wait.
func (s *Server) Run(host, port string) error {
if err := s.Start(host, port); err != nil {
return err
}
return s.Wait()
}
// Start is responsible for kicking off the http server
func (s *Server) Start(host, port string) error {
return s.StartContext(context.Background(), host, port)
}
// StartContext is responsible for kicking off the http server,
// and can be closed by cancelling the provided context
func (s *Server) StartContext(ctx context.Context, host, port string) error {
s.Infof("Fingerprint %s", s.fingerprint)
if s.users.Len() > 0 {
s.Infof("User authentication enabled")
}
if s.reverseProxy != nil {
s.Infof("Reverse proxy enabled")
}
l, err := s.listener(host, port)
if err != nil {
return err
}
h := http.Handler(http.HandlerFunc(s.handleClientHandler))
if s.Debug {
o := requestlog.DefaultOptions
o.TrustProxy = true
h = requestlog.WrapWith(h, o)
}
return s.httpServer.GoServe(ctx, l, h)
}
// Wait waits for the http server to close
func (s *Server) Wait() error {
return s.httpServer.Wait()
}
// Close forcibly closes the http server
func (s *Server) Close() error {
return s.httpServer.Close()
}
// GetFingerprint is used to access the server fingerprint
func (s *Server) GetFingerprint() string {
return s.fingerprint
}
// authUser is responsible for validating the ssh user / password combination
func (s *Server) authUser(c ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
// check if user authentication is enabled and if not, allow all
if s.users.Len() == 0 {
return nil, nil
}
// check the user exists and has matching password
n := c.User()
user, found := s.users.Get(n)
if !found || user.Pass != string(password) {
s.Debugf("Login failed for user: %s", n)
return nil, errors.New("Invalid authentication for username: %s")
}
// insert the user session map
// TODO this should probably have a lock on it given the map isn't thread-safe
s.sessions.Set(string(c.SessionID()), user)
return nil, nil
}
// AddUser adds a new user into the server user index
func (s *Server) AddUser(user, pass string, addrs ...string) error {
authorizedAddrs := []*regexp.Regexp{}
for _, addr := range addrs {
authorizedAddr, err := regexp.Compile(addr)
if err != nil {
return err
}
authorizedAddrs = append(authorizedAddrs, authorizedAddr)
}
s.users.AddUser(&settings.User{
Name: user,
Pass: pass,
Addrs: authorizedAddrs,
})
return nil
}
// DeleteUser removes a user from the server user index
func (s *Server) DeleteUser(user string) {
s.users.Del(user)
}
// ResetUsers in the server user index.
// Use nil to remove all.
func (s *Server) ResetUsers(users []*settings.User) {
s.users.Reset(users)
}