Skip to content

Commit 338fe9a

Browse files
committed
Add minimal API to query the state of the tunnel server
1 parent cf22ff5 commit 338fe9a

File tree

5 files changed

+159
-6
lines changed

5 files changed

+159
-6
lines changed

README.md

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ $ openssl req -x509 -nodes -newkey rsa:2048 -sha256 -keyout client.key -out clie
4343
$ openssl req -x509 -nodes -newkey rsa:2048 -sha256 -keyout server.key -out server.crt
4444
```
4545

46-
Run client:
46+
### Run client:
4747

4848
* Install `tunnel` binary
4949
* Make `.tunnel` directory in your project directory
@@ -55,7 +55,7 @@ Run client:
5555
$ tunnel -config ./tunnel/tunnel.yml start-all
5656
```
5757

58-
Run server:
58+
### Run server:
5959

6060
* Install `tunneld` binary
6161
* Make `.tunneld` directory
@@ -68,8 +68,6 @@ $ tunneld -tlsCrt .tunneld/server.crt -tlsKey .tunneld/server.key
6868

6969
This will run HTTP server on port `80` and HTTPS (HTTP/2) server on port `443`. If you want to use HTTPS it's recommended to get a properly signed certificate to avoid security warnings.
7070

71-
If both http and https are configured, an automatic redirect to the secure channel will be established using an `http.StatusMovedPermanently` (301)
72-
7371
### Run Server as a Service on Ubuntu using Systemd:
7472

7573
* After completing the steps above successfully, create a new file for your service (you can name it whatever you want, just replace the name below with your chosen name).
@@ -129,7 +127,7 @@ $ sudo systemctl enable tunneld.service
129127

130128
There are many more options for systemd services, and this is by not means an exhaustive configuration file.
131129

132-
## Configuration
130+
## Configuration - Client
133131

134132
The tunnel client `tunnel` requires configuration file, by default it will try reading `tunnel.yml` in your current working directory. If you want to specify other file use `-config` flag.
135133

@@ -176,10 +174,48 @@ Configuration options:
176174
* `max_interval`: maximal time client would wait before redialing the server, *default:* `1m`
177175
* `max_time`: maximal time client would try to reconnect to the server if connection was lost, set `0` to never stop trying, *default:* `15m`
178176

177+
## Configuration - Server
178+
179+
* `httpAddr`: Public address for HTTP connections, empty string to disable, *default:* `:80`
180+
* `httpsAddr`: Public address listening for HTTPS connections, emptry string to disable, *default:* `:443`
181+
* `tunnelAddr`: Public address listening for tunnel client, *default:* `:5223`
182+
* `apiAddr`: Public address for HTTP API to get info about the tunnels, *default:* `:5091`
183+
* `sniAddr`: Public address listening for TLS SNI connections, empty string to disable
184+
* `tlsCrt`: Path to a TLS certificate file, *default:* `server.crt`
185+
* `tlsKey`: Path to a TLS key file, *default:* `server.key`
186+
* `rootCA`: Path to the trusted certificate chian used for client certificate authentication, if empty any client certificate is accepted
187+
* `clients`: Comma-separated list of tunnel client ids, if empty accept all clients
188+
* `logLevel`: Level of messages to log, 0-3, *default:* 1
189+
190+
If both `httpAddr` and `httpsAddr` are configured, an automatic redirect to the secure channel will be established using an `http.StatusMovedPermanently` (301)
191+
179192
### Custom error pages
180193

181194
Just copy the `html` folder from this repository into the folder of the tunnel-server to have a starting point. In the `html/errors` folder you'll find a sample page for each error that is currently customisable which you'll be able to change according to your needs.
182195

196+
## Status API
197+
198+
### /api/clients/list
199+
200+
Returns a list of `clients` together with a list of open tunnels in JSON format.
201+
202+
```json
203+
[
204+
{
205+
"Id": "BHXWUUT-A6IYDWI-2BSIC5A-...",
206+
"Listeners": [
207+
{
208+
"Network": "tcp",
209+
"Addr": "192.0.2.1:25"
210+
}
211+
],
212+
"Hosts": [
213+
"hannes.asacloud.eu"
214+
]
215+
}
216+
]
217+
```
218+
183219
## How it works
184220

185221
A client opens TLS connection to a server. The server accepts connections from known clients only. The client is recognized by its TLS certificate ID. The server is publicly available and proxies incoming connections to the client. Then the connection is further proxied in the client's network.

cmd/tunneld/api.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (C) 2021 Tribus Hannes
2+
// Use of this source code is governed by an AGPL-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
"net/http"
11+
12+
tunnel "github.com/hons82/go-http-tunnel"
13+
"github.com/hons82/go-http-tunnel/log"
14+
)
15+
16+
// ApiConfig defines configuration for the API.
17+
type ApiConfig struct {
18+
// Addr is TCP address to listen for client connections. If empty ":0" is used.
19+
Addr string
20+
//
21+
Server *tunnel.Server
22+
// Logger is optional logger. If nil logging is disabled.
23+
Logger log.Logger
24+
}
25+
26+
func initAPIServer(config *ApiConfig) {
27+
28+
logger := config.Logger
29+
if logger == nil {
30+
logger = log.NewNopLogger()
31+
}
32+
33+
http.HandleFunc("/api/clients/list", http.HandlerFunc(
34+
func(w http.ResponseWriter, r *http.Request) {
35+
logger.Log(
36+
"level", 2,
37+
"action", "start client list",
38+
)
39+
info := config.Server.GetClientInfo()
40+
data, err := json.Marshal(info)
41+
if err != nil {
42+
w.WriteHeader(http.StatusInternalServerError)
43+
e := fmt.Sprintf("Error on unmarshall item %s", err)
44+
w.Write([]byte(e))
45+
return
46+
}
47+
w.Header().Set("Content-Type", "application/json")
48+
w.WriteHeader(http.StatusOK)
49+
w.Write(data)
50+
51+
logger.Log(
52+
"level", 3,
53+
"action", "transferred",
54+
"bytes", len(data),
55+
)
56+
},
57+
))
58+
59+
// Wrap our server with our gzip handler to gzip compress all responses.
60+
fatal("can not listen on: %s", http.ListenAndServe(config.Addr, nil))
61+
}

cmd/tunneld/options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type options struct {
4242
httpAddr string
4343
httpsAddr string
4444
tunnelAddr string
45+
apiAddr string
4546
sniAddr string
4647
tlsCrt string
4748
tlsKey string
@@ -55,6 +56,7 @@ func parseArgs() *options {
5556
httpAddr := flag.String("httpAddr", ":80", "Public address for HTTP connections, empty string to disable")
5657
httpsAddr := flag.String("httpsAddr", ":443", "Public address listening for HTTPS connections, emptry string to disable")
5758
tunnelAddr := flag.String("tunnelAddr", ":5223", "Public address listening for tunnel client")
59+
apiAddr := flag.String("apiAddr", ":5091", "Public address for HTTP API to get tunnels info")
5860
sniAddr := flag.String("sniAddr", "", "Public address listening for TLS SNI connections, empty string to disable")
5961
tlsCrt := flag.String("tlsCrt", "server.crt", "Path to a TLS certificate file")
6062
tlsKey := flag.String("tlsKey", "server.key", "Path to a TLS key file")
@@ -68,6 +70,7 @@ func parseArgs() *options {
6870
httpAddr: *httpAddr,
6971
httpsAddr: *httpsAddr,
7072
tunnelAddr: *tunnelAddr,
73+
apiAddr: *apiAddr,
7174
sniAddr: *sniAddr,
7275
tlsCrt: *tlsCrt,
7376
tlsKey: *tlsKey,

cmd/tunneld/tunneld.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ func main() {
6666
}
6767
}
6868

69+
// start API
70+
if opts.apiAddr != "" {
71+
go func() {
72+
logger.Log(
73+
"level", 1,
74+
"action", "start api",
75+
"addr", opts.apiAddr,
76+
)
77+
go initAPIServer(&ApiConfig{
78+
Addr: opts.apiAddr,
79+
Server: server,
80+
Logger: logger,
81+
})
82+
}()
83+
}
84+
6985
// start HTTP
7086
if opts.httpAddr != "" {
7187
go func() {

server.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func (s *Server) Start() {
222222
}
223223

224224
func (s *Server) handleClient(conn net.Conn) {
225-
logger := log.NewContext(s.logger).With("addr", conn.RemoteAddr())
225+
logger := log.NewContext(s.logger).With("remote addr", conn.RemoteAddr())
226226

227227
logger.Log(
228228
"level", 1,
@@ -796,3 +796,40 @@ func (s *Server) Stop() {
796796
s.listener.Close()
797797
}
798798
}
799+
800+
type ListenerInfo struct {
801+
Network string
802+
Addr string
803+
}
804+
805+
type ClientInfo struct {
806+
Id string
807+
Listeners []*ListenerInfo
808+
Hosts []string
809+
}
810+
811+
func (s *Server) GetClientInfo() []*ClientInfo {
812+
s.registry.mu.Lock()
813+
defer s.registry.mu.Unlock()
814+
ret := []*ClientInfo{}
815+
for k, v := range s.registry.items {
816+
c := &ClientInfo{Id: k.String()}
817+
ret = append(ret, c)
818+
if v == voidRegistryItem {
819+
s.logger.Log(
820+
"level", 3,
821+
"identifier", k.String(),
822+
"msg", "void registry item",
823+
)
824+
} else {
825+
for _, l := range v.Hosts {
826+
c.Hosts = append(c.Hosts, l.Host)
827+
}
828+
for _, l := range v.Listeners {
829+
p := &ListenerInfo{Network: l.Addr().Network(), Addr: l.Addr().String()}
830+
c.Listeners = append(c.Listeners, p)
831+
}
832+
}
833+
}
834+
return ret
835+
}

0 commit comments

Comments
 (0)