From 1a9c5870dd3b92b6f28e4e05ebfdd86b0c71fd13 Mon Sep 17 00:00:00 2001 From: Chris Hoage Date: Sun, 16 Nov 2025 09:51:21 -0800 Subject: [PATCH 1/2] Bump tailscale to v1.90.6 Signed-off-by: Chris Hoage --- go.mod | 28 ++-------------------------- go.sum | 12 ++---------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index a83d517..d4360e3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tailscale/caddy-tailscale -go 1.25.1 +go 1.25.3 require ( github.com/caddyserver/caddy/v2 v2.10.2 @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.7.0 github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 go.uber.org/zap v1.27.0 - tailscale.com v1.88.4 + tailscale.com v1.90.6 ) require ( @@ -30,20 +30,6 @@ require ( github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect - github.com/aws/aws-sdk-go-v2 v1.36.4 // indirect - github.com/aws/aws-sdk-go-v2/config v1.29.16 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.69 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 // indirect - github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 // indirect - github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/ccoveille/go-safecast v1.6.1 // indirect @@ -53,7 +39,6 @@ require ( github.com/chzyer/readline v1.5.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/coder/websocket v1.8.12 // indirect - github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/coreos/go-oidc/v3 v3.14.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect @@ -61,7 +46,6 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.2.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -74,7 +58,6 @@ require ( github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -85,7 +68,6 @@ require ( github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect github.com/google/go-tpm v0.9.5 // indirect github.com/google/go-tspi v0.3.0 // indirect - github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect @@ -93,13 +75,11 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -107,9 +87,7 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect - github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect @@ -151,12 +129,10 @@ require ( github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect - github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/urfave/cli v1.22.17 // indirect - github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yuin/goldmark v1.7.13 // indirect github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect diff --git a/go.sum b/go.sum index 6cdaef9..f042d3b 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,6 @@ github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVK github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -539,7 +537,6 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -692,8 +689,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -702,7 +697,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -799,8 +793,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -819,5 +811,5 @@ software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= -tailscale.com v1.88.4 h1:fXWotRMi9ZARyHRdKQa4ohXj8kqtemvvTzjreWLHVHo= -tailscale.com v1.88.4/go.mod h1:LHaTiwRgzebPDLgZ6RQQVzX+1SR5fbNl51fzm7UtMaw= +tailscale.com v1.90.6 h1:EhYPiZP/xcLeinikaLA0kn4CQT3+z9SZ13IB/kzWhd4= +tailscale.com v1.90.6/go.mod h1:+9EX6pOGCNa6pxCVRhhlJLy/qnkDzOplFYpeZyYlCT0= From 7191b0be2fd7c337b67bfb39398a276316797152 Mon Sep 17 00:00:00 2001 From: Chris Hoage Date: Sun, 16 Nov 2025 09:53:07 -0800 Subject: [PATCH 2/2] Allow AdvertiseTags to be passed to tsnet.Server Signed-off-by: Chris Hoage --- README.md | 15 ++++++++ app.go | 10 ++++++ module.go | 16 +++++++-- module_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5cbb22c..5880e44 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ go build ./cmd/caddy Multiple example configurations are provided in the [examples directory]. These examples expect an [auth key] to be set in the `TS_AUTHKEY` environment variable. +The [auth key] can optionally be an [oauth client secret] which will generate an [auth key] on demand. +In order for an [oauth client secret] to generate an [auth key] the tag that was registered with the [oauth client secret] must be provided in the caddy config. The ephemeral config option will not have an effect for oauth generated tokens. The linked documentation for [oauth client secret] indicates query parameters that can be appended to the client secret to control these settings. All nodes registered while running these examples will be ephemeral and removed after disconnect. See the comments in the individual files for details. @@ -114,6 +116,10 @@ Supported options are: # Default: false webui true|false + # If set these tags will be included when registering the node + tags tag:test + + # Any number of named node configs can be specified to override global options. { # Tailscale auth key used to register this node. @@ -134,6 +140,14 @@ Supported options are: # If true, run the Tailscale web UI for remotely managing this node. webui true|false + + # If set these tags will be included when registering the node + # Overrides global configuration tags + tags tag:test + + # If set this port will be used for tsnet. + # When unset tsnet will pick a random available port + port 4145 } } } @@ -164,6 +178,7 @@ For Caddy [JSON config], add the `tailscale` app with fields from [tscaddy.App]: [global option]: https://caddyserver.com/docs/caddyfile/options [placeholders]: https://caddyserver.com/docs/conventions#placeholders [auth key]: https://tailscale.com/kb/1085/auth-keys/ +[oauth client secret]: https://tailscale.com/kb/1215/oauth-clients#register-new-nodes-using-oauth-credentials [JSON config]: https://caddyserver.com/docs/json/ [tscaddy.App]: https://pkg.go.dev/github.com/tailscale/caddy-tailscale#App diff --git a/app.go b/app.go index 7207a2d..9f8ec4b 100644 --- a/app.go +++ b/app.go @@ -41,6 +41,9 @@ type App struct { // WebUI specifies whether Tailscale nodes should run the Web UI for remote management. WebUI bool `json:"webui,omitempty" caddy:"namespace=tailscale.webui"` + // Tags to apply to all nodes when registered. + Tags []string `json:"tags,omitempty" caddy:"namespace=tailscale.tags"` + // Nodes is a map of per-node configuration which overrides global options. Nodes map[string]Node `json:"nodes,omitempty" caddy:"namespace=tailscale"` @@ -63,6 +66,9 @@ type Node struct { // WebUI specifies whether the node should run the Web UI for remote management. WebUI opt.Bool `json:"webui,omitempty" caddy:"namespace=tailscale.webui"` + // Tags to apply to the node when registered. Overrides global tags. + Tags []string `json:"tags,omitempty" caddy:"namespace=tailscale.tags"` + // Hostname is the hostname to use when registering the node. Hostname string `json:"hostname,omitempty" caddy:"namespace=tailscale.hostname"` @@ -142,6 +148,8 @@ func parseAppConfig(d *caddyfile.Dispenser, _ any) (any, error) { } else { app.WebUI = true } + case "tags": + app.Tags = d.RemainingArgs() default: node, err := parseNodeConfig(d) if app.Nodes == nil { @@ -223,6 +231,8 @@ func parseNodeConfig(d *caddyfile.Dispenser) (Node, error) { } else { node.WebUI = opt.NewBool(true) } + case "tags": + node.Tags = segment.RemainingArgs() default: return node, segment.Errf("unrecognized subdirective: %s", segment.Val()) } diff --git a/module.go b/module.go index 9e1ed25..a9b42d0 100644 --- a/module.go +++ b/module.go @@ -272,9 +272,10 @@ func getNode(ctx caddy.Context, name string) (*tailscaleNode, error) { UserLogf: func(format string, args ...any) { app.logger.Sugar().Infof(format, args...) }, - Ephemeral: getEphemeral(name, app), - RunWebClient: getWebUI(name, app), - Port: getPort(name, app), + Ephemeral: getEphemeral(name, app), + RunWebClient: getWebUI(name, app), + Port: getPort(name, app), + AdvertiseTags: getTags(name, app), } if s.AuthKey, err = getAuthKey(name, app); err != nil { @@ -401,6 +402,15 @@ func getWebUI(name string, app *App) bool { return app.WebUI } +func getTags(name string, app *App) []string { + if node, ok := app.Nodes[name]; ok { + if node.Tags != nil { + return node.Tags + } + } + return app.Tags +} + // tailscaleNode is a wrapper around a tsnet.Server that provides a fully self-contained Tailscale node. // This node can listen on the tailscale network interface, or be used to connect to other nodes in the tailnet. type tailscaleNode struct { diff --git a/module_test.go b/module_test.go index 1da03ce..6645567 100644 --- a/module_test.go +++ b/module_test.go @@ -333,6 +333,98 @@ func Test_GetWebUI(t *testing.T) { } } +func Test_GetTags(t *testing.T) { + tests := map[string]struct { + appTags []string + nodeTags []string + want []string + }{ + "no tags": { + appTags: nil, + nodeTags: nil, + want: nil, + }, + "app-level tags only": { + appTags: []string{"tag:test1", "tag:test2"}, + nodeTags: nil, + want: []string{"tag:test1", "tag:test2"}, + }, + "node-level tags override app tags": { + appTags: []string{"tag:app1", "tag:app2"}, + nodeTags: []string{"tag:node1", "tag:node2"}, + want: []string{"tag:node1", "tag:node2"}, + }, + "empty node tags override app tags": { + appTags: []string{"tag:app1", "tag:app2"}, + nodeTags: []string{}, + want: []string{}, + }, + "single tag": { + appTags: []string{"tag:production"}, + nodeTags: nil, + want: []string{"tag:production"}, + }, + "multiple node tags": { + appTags: nil, + nodeTags: []string{"tag:web", "tag:frontend", "tag:production"}, + want: []string{"tag:web", "tag:frontend", "tag:production"}, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + app := &App{ + Tags: tt.appTags, + Nodes: make(map[string]Node), + } + if tt.nodeTags != nil { + app.Nodes["testnode"] = Node{ + Tags: tt.nodeTags, + } + } + if err := app.Provision(caddy.Context{}); err != nil { + t.Fatal(err) + } + + got := getTags("testnode", app) + if len(got) != len(tt.want) { + t.Errorf("getTags() = %v, want %v", got, tt.want) + return + } + for i := range got { + if got[i] != tt.want[i] { + t.Errorf("getTags() = %v, want %v", got, tt.want) + return + } + } + }) + } + + // Test node without config gets app-level tags + t.Run("node without config uses app tags", func(t *testing.T) { + app := &App{ + Tags: []string{"tag:default1", "tag:default2"}, + Nodes: make(map[string]Node), + } + if err := app.Provision(caddy.Context{}); err != nil { + t.Fatal(err) + } + + got := getTags("unconfigured-node", app) + want := []string{"tag:default1", "tag:default2"} + if len(got) != len(want) { + t.Errorf("getTags() = %v, want %v", got, want) + return + } + for i := range got { + if got[i] != want[i] { + t.Errorf("getTags() = %v, want %v", got, want) + return + } + } + }) +} + func Test_Listen(t *testing.T) { must.Do(caddy.Run(new(caddy.Config))) ctx := caddy.ActiveContext()