Home

tailroute @main - refs - log -
-
https://git.jolheiser.com/tailroute.git
Router for tailnet and funnel across tailscale
tree log patch
tailgrok Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQPzdlJgZYByESvhtv7WBX2Kn/+Zx3sPDqAVpxAu6Cu0uDHfyHAlyAoFfEShVEjIaWN jLkfp4+4qhYM1ve7Wb8gg= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
1 week ago
4 changed files, 167 additions(+), 19 deletions(-)
cmd/tailgrok/main.gocmd/tailproxy/main.gotailproxy.gotailroute.go
I cmd/tailgrok/main.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
diff --git a/cmd/tailgrok/main.go b/cmd/tailgrok/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..02fb769e7765d0055f91efa3769e3edeca336b40
--- /dev/null
+++ b/cmd/tailgrok/main.go
@@ -0,0 +1,130 @@
+package main
+
+import (
+	"bytes"
+	"context"
+	"crypto/rand"
+	"encoding/hex"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"net/http"
+	"net/url"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"go.jolheiser.com/tailroute"
+	"golang.org/x/oauth2/clientcredentials"
+)
+
+func main() {
+	fs := flag.NewFlagSet("tailgrok", flag.ExitOnError)
+	if err := fs.Parse(os.Args[1:]); err != nil {
+		panic(err)
+	}
+
+	if fs.NArg() < 1 {
+		panic("tailgrok requires a target URL to proxy")
+	}
+
+	target, err := url.Parse(fs.Arg(0))
+	if err != nil {
+		panic(err)
+	}
+
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		panic(err)
+	}
+	cfgPath := filepath.Join(homeDir, ".config", "tailgrok", "config.json")
+
+	b, err := os.ReadFile(cfgPath)
+	if err != nil {
+		panic(err)
+	}
+	var cfg Config
+	if err := json.Unmarshal(b, &cfg); err != nil {
+		panic(err)
+	}
+
+	clientSecret, err := cfg.ClientSecret()
+	if err != nil {
+		panic(err)
+	}
+
+	oauthConfig := &clientcredentials.Config{
+		ClientID:     cfg.ClientID,
+		ClientSecret: clientSecret,
+		TokenURL:     "https://api.tailscale.com/api/v2/oauth/token",
+	}
+	client := oauthConfig.Client(context.Background())
+
+	payload, err := json.Marshal(M{
+		"description": "tailgrok",
+		"capabilities": M{
+			"devices": M{
+				"create": M{
+					"reusable":      false,
+					"ephemeral":     true,
+					"preauthorized": true,
+					"tags":          []string{"tag:funnel", "tag:tailgrok"},
+				},
+			},
+		},
+		"expirySeconds": 5,
+	})
+	if err != nil {
+		panic(err)
+	}
+	authTokenReq, err := http.NewRequest(http.MethodPost, "https://api.tailscale.com/api/v2/tailnet/-/keys", bytes.NewReader(payload))
+	if err != nil {
+		panic(err)
+	}
+	resp, err := client.Do(authTokenReq)
+	if err != nil {
+		panic(err)
+	}
+	defer resp.Body.Close()
+	tsKey := struct {
+		Key string `json:"key"`
+	}{}
+	if err := json.NewDecoder(resp.Body).Decode(&tsKey); err != nil {
+		panic(err)
+	}
+	os.Setenv("TS_AUTHKEY", tsKey.Key)
+
+	tmp, err := os.MkdirTemp(os.TempDir(), "tailgrok-*")
+	if err != nil {
+		panic(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	random := make([]byte, 8)
+	if _, err := rand.Read(random); err != nil {
+		panic(err)
+	}
+	id := hex.EncodeToString(random)
+
+	fmt.Printf("https://%s.serval-vibes.ts.net\n", id)
+	r := tailroute.Proxy(target, true)
+	r.Ephemeral = true
+	if err := r.Serve(id, tmp); err != nil {
+		panic(err)
+	}
+}
+
+type M = map[string]any
+
+type Config struct {
+	ClientID         string `json:"clientID"`
+	ClientSecretFile string `json:"clientSecretFile"`
+}
+
+func (c Config) ClientSecret() (string, error) {
+	b, err := os.ReadFile(c.ClientSecretFile)
+	if err != nil {
+		return "", err
+	}
+	return strings.TrimSpace(string(b)), nil
+}
M cmd/tailproxy/main.go -> cmd/tailproxy/main.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
diff --git a/cmd/tailproxy/main.go b/cmd/tailproxy/main.go
index fd08e895ac2ae318c3c552ac524cd96578671279..9b658d80932c62e62834ed835ce415b474558277 100644
--- a/cmd/tailproxy/main.go
+++ b/cmd/tailproxy/main.go
@@ -3,8 +3,6 @@
 import (
 	"flag"
 	"fmt"
-	"net/http"
-	"net/http/httputil"
 	"net/url"
 	"os"
 
@@ -40,16 +38,7 @@ 	if err != nil {
 		panic(err)
 	}
 
-	proxy := httputil.NewSingleHostReverseProxy(proxyURL)
-	handler := http.HandlerFunc(proxy.ServeHTTP)
-
-	r := tailroute.Router{
-		Tailnet: handler,
-	}
-	if args.Funnel {
-		r.Funnel = handler
-	}
-
+	r := tailroute.Proxy(proxyURL, args.Funnel)
 	if err := r.Serve(args.Hostname, args.DataDir); err != nil {
 		panic(err)
 	}
I tailproxy.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
diff --git a/tailproxy.go b/tailproxy.go
new file mode 100644
index 0000000000000000000000000000000000000000..462a3d6803b83480e2fa8d914834f81b4bfff3da
--- /dev/null
+++ b/tailproxy.go
@@ -0,0 +1,26 @@
+package tailroute
+
+import (
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+)
+
+func Proxy(target *url.URL, funnel bool) Router {
+	proxy := httputil.NewSingleHostReverseProxy(target)
+	proxyDirector := proxy.Director
+	proxy.Director = func(r *http.Request) {
+		proxyDirector(r)
+		r.Host = target.Host
+		r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
+	}
+	handler := http.HandlerFunc(proxy.ServeHTTP)
+
+	r := Router{
+		Tailnet: handler,
+	}
+	if funnel {
+		r.Funnel = handler
+	}
+	return r
+}
M tailroute.go -> tailroute.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
diff --git a/tailroute.go b/tailroute.go
index 98ed6f7df00cfdc912cb6fa6c1201549496d3790..f14ce246a4abd1478cb383469ec13a365a6256e1 100644
--- a/tailroute.go
+++ b/tailroute.go
@@ -24,9 +24,10 @@ 	isTailnet
 )
 
 type Router struct {
-	Funnel  http.Handler
-	Tailnet http.Handler
-	Logger  logger.Logf
+	Funnel    http.Handler
+	Tailnet   http.Handler
+	Logger    logger.Logf
+	Ephemeral bool
 }
 
 func (router Router) serve(ln net.Listener) error {
@@ -84,13 +85,15 @@ 	if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
 		return fmt.Errorf("could not create data dir: %w", err)
 	}
 	s := &tsnet.Server{
-		Hostname: hostname,
-		Dir:      dataDir,
-		Logf:     router.Logger,
+		Hostname:  hostname,
+		Dir:       dataDir,
+		Logf:      router.Logger,
+		Ephemeral: router.Ephemeral,
 	}
 	if err := s.Start(); err != nil {
 		return err
 	}
+	defer s.Close()
 
 	lc, err := s.LocalClient()
 	if err != nil {
@@ -125,7 +128,7 @@ 	var lnHttps net.Listener
 	if router.Funnel == nil {
 		lnHttps, err = s.ListenTLS("tcp", ":443")
 	} else {
-		lnHttps, err = s.ListenFunnel("tcp", ":443")
+		lnHttps, err = s.ListenFunnel("tcp", ":80")
 	}
 	if err != nil {
 		return fmt.Errorf("can't listen https: %w", err)