1
1
package main
2
2
3
3
import (
4
+ "bytes"
4
5
"crypto/tls"
5
6
"crypto/x509"
6
7
"encoding/json"
@@ -17,6 +18,7 @@ import (
17
18
"path/filepath"
18
19
"strings"
19
20
"sync"
21
+ "time"
20
22
21
23
tunnel "git.sequentialread.com/forest/tunnel/tunnel-lib"
22
24
)
@@ -42,10 +44,11 @@ type ClientConfig struct {
42
44
ClientIdentifier string
43
45
ServerAddr string
44
46
UseTls bool
45
- ServiceToLocalAddrMap map [string ]string
47
+ ServiceToLocalAddrMap * map [string ]string
46
48
CaCertificateFilesGlob string
47
49
ClientTlsKeyFile string
48
50
ClientTlsCertificateFile string
51
+ AdminUnixSocket string
49
52
}
50
53
51
54
type ListenerConfig struct {
@@ -66,6 +69,13 @@ type ManagementHttpHandler struct {
66
69
ControlHandler http.Handler
67
70
}
68
71
72
+ type LiveConfigUpdate struct {
73
+ Listeners []ListenerConfig
74
+ ServiceToLocalAddrMap map [string ]string
75
+ }
76
+
77
+ type adminAPI struct {}
78
+
69
79
// Server State
70
80
var listeners []ListenerConfig
71
81
var clientStatesMutex = & sync.Mutex {}
@@ -74,6 +84,9 @@ var server *tunnel.Server
74
84
75
85
// Client State
76
86
var client * tunnel.Client
87
+ var tlsClientConfig * tls.Config
88
+ var serverURL * string
89
+ var serviceToLocalAddrMap * map [string ]string
77
90
78
91
func main () {
79
92
@@ -94,6 +107,88 @@ func main() {
94
107
95
108
}
96
109
110
+ // admin api handler for /liveconfig over unix socket
111
+ func (handler adminAPI ) ServeHTTP (response http.ResponseWriter , request * http.Request ) {
112
+ switch path .Clean (request .URL .Path ) {
113
+ case "/liveconfig" :
114
+ if request .Method == "PUT" {
115
+ requestBytes , err := ioutil .ReadAll (request .Body )
116
+ if err != nil {
117
+ log .Printf ("adminAPI: request read error: %+v\n \n " , err )
118
+ http .Error (response , "500 request read error" , http .StatusInternalServerError )
119
+ return
120
+ }
121
+ var configUpdate LiveConfigUpdate
122
+ err = json .Unmarshal (requestBytes , & configUpdate )
123
+ if err != nil {
124
+ log .Printf ("adminAPI: can't parse JSON: %+v\n \n " , err )
125
+ http .Error (response , "400 bad request: can't parse JSON" , http .StatusBadRequest )
126
+ return
127
+ }
128
+
129
+ sendBytes , err := json .Marshal (configUpdate .Listeners )
130
+ if err != nil {
131
+ log .Printf ("adminAPI: Listeners json serialization failed: %+v\n \n " , err )
132
+ http .Error (response , "500 Listeners json serialization failed" , http .StatusInternalServerError )
133
+ return
134
+ }
135
+ apiURL := fmt .Sprintf ("https://%s/tunnels" , * serverURL )
136
+ tunnelsRequest , err := http .NewRequest ("PUT" , apiURL , bytes .NewReader (sendBytes ))
137
+ if err != nil {
138
+ log .Printf ("adminAPI: error creating tunnels request: %+v\n \n " , err )
139
+ http .Error (response , "500 error creating tunnels request" , http .StatusInternalServerError )
140
+ return
141
+ }
142
+ tunnelsRequest .Header .Add ("content-type" , "application/json" )
143
+
144
+ client := & http.Client {
145
+ Transport : & http.Transport {
146
+ TLSClientConfig : tlsClientConfig ,
147
+ },
148
+ Timeout : 10 * time .Second ,
149
+ }
150
+ tunnelsResponse , err := client .Do (tunnelsRequest )
151
+ if err != nil {
152
+ log .Printf ("adminAPI: Do(tunnelsRequest): %+v\n \n " , err )
153
+ http .Error (response , "502 tunnels request failed" , http .StatusBadGateway )
154
+ return
155
+ }
156
+ tunnelsResponseBytes , err := ioutil .ReadAll (tunnelsResponse .Body )
157
+ if err != nil {
158
+ log .Printf ("adminAPI: tunnelsResponse read error: %+v\n \n " , err )
159
+ http .Error (response , "502 tunnelsResponse read error" , http .StatusBadGateway )
160
+ return
161
+ }
162
+
163
+ if tunnelsResponse .StatusCode != http .StatusOK {
164
+ log .Printf (
165
+ "adminAPI: tunnelsRequest returned HTTP %d: %s\n \n " ,
166
+ tunnelsResponse .StatusCode , string (tunnelsResponseBytes ),
167
+ )
168
+ http .Error (
169
+ response ,
170
+ fmt .Sprintf ("502 tunnels request returned HTTP %d" , tunnelsResponse .StatusCode ),
171
+ http .StatusBadGateway ,
172
+ )
173
+ return
174
+ }
175
+
176
+ serviceToLocalAddrMap = & configUpdate .ServiceToLocalAddrMap
177
+
178
+ response .Header ().Add ("content-type" , "application/json" )
179
+ response .WriteHeader (http .StatusOK )
180
+ response .Write (tunnelsResponseBytes )
181
+
182
+ } else {
183
+ response .Header ().Set ("Allow" , "PUT" )
184
+ http .Error (response , "405 method not allowed, try PUT" , http .StatusMethodNotAllowed )
185
+ }
186
+ default :
187
+ http .Error (response , "404 not found, try PUT /liveconfig" , http .StatusNotFound )
188
+ }
189
+
190
+ }
191
+
97
192
func runClient (configFileName * string ) {
98
193
99
194
configBytes := getConfigBytes (configFileName )
@@ -103,13 +198,16 @@ func runClient(configFileName *string) {
103
198
if err != nil {
104
199
log .Fatalf ("runClient(): can't json.Unmarshal(configBytes, &config) because %s \n " , err )
105
200
}
201
+ serviceToLocalAddrMap = config .ServiceToLocalAddrMap
202
+ serverURL = & config .ServerAddr
106
203
107
204
configToLog , _ := json .MarshalIndent (config , "" , " " )
108
205
log .Printf ("theshold client is starting up using config:\n %s\n " , string (configToLog ))
109
206
110
207
dialFunction := net .Dial
111
208
112
209
if config .UseTls {
210
+
113
211
cert , err := tls .LoadX509KeyPair (config .ClientTlsCertificateFile , config .ClientTlsKeyFile )
114
212
if err != nil {
115
213
log .Fatal (err )
@@ -129,41 +227,81 @@ func runClient(configFileName *string) {
129
227
caCertPool .AppendCertsFromPEM (caCert )
130
228
}
131
229
132
- tlsConfig : = & tls.Config {
230
+ tlsClientConfig = & tls.Config {
133
231
Certificates : []tls.Certificate {cert },
134
232
RootCAs : caCertPool ,
135
233
}
136
- tlsConfig .BuildNameToCertificate ()
234
+ tlsClientConfig .BuildNameToCertificate ()
137
235
138
236
dialFunction = func (network , address string ) (net.Conn , error ) {
139
- return tls .Dial (network , address , tlsConfig )
237
+ return tls .Dial (network , address , tlsClientConfig )
140
238
}
141
239
}
142
240
241
+ clientStateChanges := make (chan * tunnel.ClientStateChange )
143
242
tunnelClientConfig := & tunnel.ClientConfig {
144
243
DebugLog : config .DebugLog ,
145
244
Identifier : config .ClientIdentifier ,
146
245
ServerAddr : config .ServerAddr ,
147
246
FetchLocalAddr : func (service string ) (string , error ) {
148
- localAddr , hasLocalAddr := config .ServiceToLocalAddrMap [service ]
247
+ //log.Printf("(*serviceToLocalAddrMap): %+v\n\n", (*serviceToLocalAddrMap))
248
+ localAddr , hasLocalAddr := (* serviceToLocalAddrMap )[service ]
149
249
if ! hasLocalAddr {
150
250
return "" , errors .New ("service not configured. See ServiceToLocalAddrMap in client config file." )
151
251
}
152
252
return localAddr , nil
153
253
},
154
- Dial : dialFunction ,
254
+ Dial : dialFunction ,
255
+ StateChanges : clientStateChanges ,
155
256
}
156
257
157
258
client , err = tunnel .NewClient (tunnelClientConfig )
158
259
if err != nil {
159
260
log .Fatalf ("runClient(): can't create tunnel client because %s \n " , err )
160
261
}
161
262
263
+ go (func () {
264
+ for {
265
+ stateChange := <- clientStateChanges
266
+ fmt .Printf ("clientStateChange: %s\n " , stateChange .String ())
267
+ }
268
+ })()
269
+
270
+ go runClientAdminApi (config )
271
+
162
272
fmt .Print ("runClient(): the client should be running now\n " )
163
273
client .Start ()
164
274
165
275
}
166
276
277
+ func runClientAdminApi (config ClientConfig ) {
278
+
279
+ os .Remove (config .AdminUnixSocket )
280
+
281
+ listenAddress , err := net .ResolveUnixAddr ("unix" , config .AdminUnixSocket )
282
+ if err != nil {
283
+ panic (fmt .Sprintf ("runClient(): can't start because net.ResolveUnixAddr() returned %+v" , err ))
284
+ }
285
+
286
+ listener , err := net .ListenUnix ("unix" , listenAddress )
287
+ if err != nil {
288
+ panic (fmt .Sprintf ("can't start because net.ListenUnix() returned %+v" , err ))
289
+ }
290
+ log .Printf ("AdminUnixSocket Listening: %v\n \n " , config .AdminUnixSocket )
291
+ defer listener .Close ()
292
+
293
+ server := http.Server {
294
+ Handler : adminAPI {},
295
+ ReadTimeout : 10 * time .Second ,
296
+ WriteTimeout : 10 * time .Second ,
297
+ }
298
+
299
+ err = server .Serve (listener )
300
+ if err != nil {
301
+ panic (fmt .Sprintf ("AdminUnixSocket server returned %+v" , err ))
302
+ }
303
+ }
304
+
167
305
func runServer (configFileName * string ) {
168
306
169
307
configBytes := getConfigBytes (configFileName )
@@ -345,8 +483,8 @@ func compareListenerConfigs(a, b ListenerConfig) bool {
345
483
346
484
func (s * ManagementHttpHandler ) ServeHTTP (responseWriter http.ResponseWriter , request * http.Request ) {
347
485
348
- switch fmt . Sprintf ( "%s/" , path .Clean (request .URL .Path ) ) {
349
- case "/clients/ " :
486
+ switch path .Clean (request .URL .Path ) {
487
+ case "/clients" :
350
488
if request .Method == "GET" {
351
489
clientStatesMutex .Lock ()
352
490
bytes , err := json .Marshal (clientStates )
@@ -362,7 +500,7 @@ func (s *ManagementHttpHandler) ServeHTTP(responseWriter http.ResponseWriter, re
362
500
responseWriter .Header ().Set ("Allow" , "PUT" )
363
501
http .Error (responseWriter , "405 Method Not Allowed" , http .StatusMethodNotAllowed )
364
502
}
365
- case "/tunnels/ " :
503
+ case "/tunnels" :
366
504
if request .Method == "PUT" {
367
505
if request .Header .Get ("Content-Type" ) != "application/json" {
368
506
http .Error (responseWriter , "415 Unsupported Media Type: Content-Type must be application/json" , http .StatusUnsupportedMediaType )
@@ -399,7 +537,7 @@ func (s *ManagementHttpHandler) ServeHTTP(responseWriter http.ResponseWriter, re
399
537
responseWriter .Header ().Set ("Allow" , "PUT" )
400
538
http .Error (responseWriter , "405 Method Not Allowed" , http .StatusMethodNotAllowed )
401
539
}
402
- case "/ping/ " :
540
+ case "/ping" :
403
541
if request .Method == "GET" {
404
542
fmt .Fprint (responseWriter , "pong" )
405
543
} else {
0 commit comments