Skip to content

Commit 2bca504

Browse files
DAcodedBEATArun Philip
authored andcommitted
feat: add OAuth client secret authentication support
- Add OAuth client secret authentication as alternative to traditional auth keys - Update documentation with OAuth configuration and minor cleanup - Upgrade to Golang 1.25.1 (required for upgraded tailscale dep for oauth support) Signed-off-by: Arun Philip <[email protected]>
1 parent 2b9bb90 commit 2bca504

File tree

7 files changed

+70
-23
lines changed

7 files changed

+70
-23
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# docker buildx build --platform linux/amd64 -t tsidp-server:amd64 --load .
77

88
# Build stage
9-
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
9+
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
1010
WORKDIR /app
1111

1212
# BuildKit will set these automatically when using buildx
@@ -37,4 +37,4 @@ COPY scripts/docker/run.sh /run.sh
3737
RUN chmod +x /run.sh
3838

3939
# Run the binary through the entrypoint script
40-
ENTRYPOINT ["/run.sh"]
40+
ENTRYPOINT ["/run.sh"]

README.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ docker run -d \
4444
-e TAILSCALE_USE_WIP_CODE=1 \
4545
-e TS_STATE_DIR=/data \
4646
-e TS_HOSTNAME=idp \
47+
-e TS_AUTHKEY=YOUR_TAILSCALE_AUTHKEY \
4748
-e TSIDP_ENABLE_STS=1 \
4849
ghcr.io/tailscale/tsidp:latest
4950
```
@@ -53,6 +54,32 @@ Visit `https://idp.yourtailnet.ts.net` to confirm the service is running.
5354
> [!NOTE]
5455
> If you're running tsidp for the first time it may take a few minutes for the TLS certificate to generate. You may not be able to access the service until the certificate is ready.
5556
57+
#### Using OAuth Client Secrets
58+
59+
As an alternative to traditional auth keys, you can use OAuth client secrets for authentication by passing them through `TS_AUTHKEY`:
60+
61+
```bash
62+
# Run tsidp with OAuth client secret
63+
docker run -d \
64+
--name tsidp \
65+
-p 443:443 \
66+
-v tsidp-data:/data \
67+
-e TAILSCALE_USE_WIP_CODE=1 \
68+
-e TS_STATE_DIR=/data \
69+
-e TS_HOSTNAME=idp \
70+
-e TSIDP_ENABLE_STS=1 \
71+
-e TS_AUTHKEY=tskey-client-xxxxxxxxxxxx \
72+
-e TS_ADVERTISE_TAGS=tag:tsidp,tag:server \
73+
ghcr.io/tailscale/tsidp:latest
74+
```
75+
76+
> [!IMPORTANT]
77+
> When using OAuth client secrets:
78+
> - Pass the OAuth client secret through `TS_AUTHKEY` (same as regular auth keys)
79+
> - Specify advertise tags using `TS_ADVERTISE_TAGS`
80+
> - The OAuth client secret must start with `tskey-client-`
81+
> - The tags must be properly configured in your Tailscale ACL policy
82+
5683
### Other Ways to Build and Run
5784

5885
<details>
@@ -75,7 +102,7 @@ $ git clone https://github.com/tailscale/tsidp.git
75102
$ cd tsidp
76103

77104
# run with default values for flags
78-
$ TAILSCALE_USE_WIP_CODE=1 TS_AUTHKEY={YOUR_TAILSCALE_AUTHKEY} TSNET_FORCE_LOGIN=1 go run .
105+
$ TAILSCALE_USE_WIP_CODE=1 TS_AUTHKEY=YOUR_TAILSCALE_AUTHKEY TSNET_FORCE_LOGIN=1 go run .
79106
```
80107

81108
</details>
@@ -118,7 +145,7 @@ This is a permissive grant that is suitable for testing purposes:
118145
The `tsidp-server` is configured by several command-line flags:
119146

120147
| Flag | Description | Default |
121-
| ----------------------- | -------------------------------------------------------------------------------------------------- | -------- |
148+
| ------------------------| -------------------------------------------------------------------------------------------------- | -------- |
122149
| `-dir <path>` | Directory path to save tsnet and tsidp state. Recommend to be set. | `""` |
123150
| `-hostname <hostname>` | hostname on tailnet. Will become `<hostname>.your-tailnet.ts.net` | `idp` |
124151
| `-port <port>` | Port to listen on | `443` |
@@ -142,7 +169,11 @@ The `tsidp-server` binary is configured through the CLI flags above. However, th
142169

143170
These environment variables are used when tsidp does not have any state information set in `-dir <path>`.
144171

145-
- `TS_AUTHKEY=<key>`: Key for registering a tsidp as a new node on your tailnet. If omitted a link will be printed to manually register.
172+
> [!WARNING]
173+
> **Serverless/Stateless Deployment**: tsidp requires persistent state storage to function properly in production. Without a persistent `-dir`, the service will re-register with Tailscale on every restart, lose dynamic OIDC client registrations, and invalidate user sessions. Serverless environments without persistent storage are not recommended for production use.
174+
175+
- `TS_AUTHKEY=<key>`: Key for registering a tsidp as a new node on your tailnet. Can be a traditional auth key or OAuth client secret (tskey-client-xxx). If omitted, a link will be printed to manually register.
176+
- `TS_ADVERTISE_TAGS=<tags>`: Comma-separated advertise tags (e.g., "tag:tsidp,tag:server"). Optional, but recommended when using OAuth client secrets.
146177
- `TSNET_FORCE_LOGIN=1`: Force re-login of the node. Useful during development.
147178

148179
### Docker Environment Variables
@@ -162,13 +193,15 @@ The Docker image exposes the CLI flags through environment variables. If omitted
162193
| `TSIDP_LOG=<level>` | `-log <level>` |
163194
| `TSIDP_DEBUG_TSNET=1` | `-debug-tsnet` |
164195
| `TSIDP_DEBUG_ALL_REQUESTS=1` | `-debug-all-requests` |
196+
| `TS_AUTHKEY=<key>` | _(env var only)_ |
197+
| `TS_ADVERTISE_TAGS=<tags>` | _(env var only)_ |
165198

166199
## Application Configuration Guides (WIP)
167200

168201
tsidp can be used as IdP server for any application that supports custom OIDC providers.
169202

170203
> [!IMPORTANT]
171-
> Note: If you'd like to use tsidp to login to a SaaS application outside of your tailnet rather than a self-hosted app inside of your tailnet, you'll need to run tsidp with `--funnel` enabled.
204+
> Note: If you'd like to use tsidp to login to a SaaS application outside of your tailnet rather than a self-hosted app inside of your tailnet, you'll need to run tsidp with `-funnel` enabled.
172205
173206
- [Proxmox](docs/proxmox/README.md)
174207

flake.nix

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
nixpkgs,
1212
systems,
1313
}: let
14-
go125Version = "1.24.7";
15-
goHash = "sha256-Ko9Q2w+IgDYHxQ1+qINNy3vUg8a0KKkeNg/fhiS0ZGQ=";
14+
go125Version = "1.25.1";
15+
goHash = "sha256-0BDBCc7pTYDv5oHqtGvepJGskGv0ZYPDLp8NuwvRpZQ=";
1616
eachSystem = f:
1717
nixpkgs.lib.genAttrs (import systems) (system:
1818
f (import nixpkgs {
1919
system = system;
2020
overlays = [
2121
(final: prev: {
22-
go_1_24 = prev.go_1_24.overrideAttrs {
22+
go_1_25 = prev.go_1_25.overrideAttrs {
2323
version = go125Version;
2424
src = prev.fetchurl {
2525
url = "https://go.dev/dl/go${go125Version}.src.tar.gz";
@@ -33,7 +33,7 @@
3333
formatter = eachSystem (pkgs: pkgs.nixpkgs-fmt);
3434

3535
packages = eachSystem (pkgs: {
36-
default = pkgs.buildGo124Module {
36+
default = pkgs.buildGo125Module {
3737
pname = "tsidp";
3838
version =
3939
if (self ? shortRev)

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
module github.com/tailscale/tsidp
22

3-
go 1.24.7
3+
go 1.25.1
44

55
require (
66
gopkg.in/square/go-jose.v2 v2.6.0
7-
tailscale.com v1.86.5
7+
tailscale.com v1.88.3
88
)
99

1010
require (
@@ -31,7 +31,7 @@ require (
3131
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
3232
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
3333
github.com/gaissmai/bart v0.18.0 // indirect
34-
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect
34+
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
3535
github.com/go-ole/go-ole v1.3.0 // indirect
3636
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
3737
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -60,7 +60,7 @@ require (
6060
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
6161
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
6262
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
63-
github.com/vishvananda/netns v0.0.4 // indirect
63+
github.com/vishvananda/netns v0.0.5 // indirect
6464
github.com/x448/float16 v0.8.4 // indirect
6565
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
6666
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo=
6767
github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY=
6868
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
6969
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
70-
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY=
71-
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
70+
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
71+
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
7272
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
7373
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
7474
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
@@ -176,8 +176,8 @@ github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1
176176
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
177177
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
178178
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
179-
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
180-
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
179+
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
180+
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
181181
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
182182
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
183183
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
@@ -218,8 +218,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
218218
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
219219
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
220220
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
221-
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
222-
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
221+
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
222+
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
223223
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
224224
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
225225
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@@ -236,5 +236,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
236236
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
237237
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
238238
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
239-
tailscale.com v1.86.5 h1:yBtWFjuLYDmxVnfnvPbZNZcKADCYgNfMd0rUAOA9XCs=
240-
tailscale.com v1.86.5/go.mod h1:Lm8dnzU2i/Emw15r6sl3FRNp/liSQ/nYw6ZSQvIdZ1M=
239+
tailscale.com v1.88.3 h1:OiE6iVqzykhbITxmIKjH8d00cw0LsJFO3TuFd4jQVXU=
240+
tailscale.com v1.88.3/go.mod h1:LHaTiwRgzebPDLgZ6RQQVzX+1SR5fbNl51fzm7UtMaw=

scripts/docker/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ if [ -n "$TSIDP_DEBUG_TSNET" ]; then
5050
fi
5151

5252
# Execute tsidp-server with the built arguments
53-
exec /tsidp-server $ARGS "$@"
53+
exec /tsidp-server $ARGS "$@"

tsidp-server.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var (
5858
func main() {
5959
flag.Parse()
6060
ctx := context.Background()
61+
6162
if !envknob.UseWIPCode() {
6263
slog.Error("cmd/tsidp is a work in progress and has not been security reviewed;\nits use requires TAILSCALE_USE_WIP_CODE=1 be set in the environment for now.")
6364
os.Exit(1)
@@ -131,23 +132,36 @@ func main() {
131132
Hostname: *flagHostname,
132133
Dir: *flagDir,
133134
}
135+
136+
if advertiseTags := os.Getenv("TS_ADVERTISE_TAGS"); advertiseTags != "" {
137+
tags := strings.Split(advertiseTags, ",")
138+
for i, tag := range tags {
139+
tags[i] = strings.TrimSpace(tag)
140+
}
141+
ts.AdvertiseTags = tags
142+
slog.Info("Using advertise tags", slog.String("tags", strings.Join(tags, ",")))
143+
}
144+
134145
if *flagDebugTSNet {
135146
ts.Logf = func(format string, args ...any) {
136147
cur := slog.SetLogLoggerLevel(slog.LevelDebug) // force debug if this option is on
137148
slog.Debug(fmt.Sprintf(format, args...))
138149
slog.SetLogLoggerLevel(cur)
139150
}
140151
}
152+
141153
st, err = ts.Up(ctx)
142154
if err != nil {
143155
slog.Error("failed to start tsnet server", slog.Any("error", err))
144156
os.Exit(1)
145157
}
158+
146159
lc, err = ts.LocalClient()
147160
if err != nil {
148161
slog.Error("failed to get local client", slog.Any("error", err))
149162
os.Exit(1)
150163
}
164+
151165
var ln net.Listener
152166
if *flagFunnel {
153167
if err := ipn.CheckFunnelAccess(uint16(*flagPort), st.Self); err != nil {

0 commit comments

Comments
 (0)