cmd/tsidp,tsnet: update tsidp oidc-key store path (#16735)

The tsidp oidc-key.json ended up in the root directory
or home dir of the user process running it.

Update this to store it in a known location respecting
the TS_STATE_DIR and flagDir options.

Fixes #16734

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
This commit is contained in:
Mike O'Driscoll 2025-07-31 12:13:36 -04:00 committed by GitHub
parent 1cc842b389
commit 47b5f10165
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 3 deletions

View File

@ -29,6 +29,7 @@
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -60,6 +61,9 @@ type ctxConn struct{
// accessing the IDP over Funnel are persisted. // accessing the IDP over Funnel are persisted.
const funnelClientsFile = "oidc-funnel-clients.json" const funnelClientsFile = "oidc-funnel-clients.json"
// oidcKeyFile is where the OIDC private key is persisted.
const oidcKeyFile = "oidc-key.json"
var ( var (
flagVerbose = flag.Bool("verbose", false, "be verbose") flagVerbose = flag.Bool("verbose", false, "be verbose")
flagPort = flag.Int("port", 443, "port to listen on") flagPort = flag.Int("port", 443, "port to listen on")
@ -80,12 +84,14 @@ func main() {
var ( var (
lc *local.Client lc *local.Client
st *ipnstate.Status st *ipnstate.Status
rootPath string
err error err error
watcherChan chan error watcherChan chan error
cleanup func() cleanup func()
lns []net.Listener lns []net.Listener
) )
if *flagUseLocalTailscaled { if *flagUseLocalTailscaled {
lc = &local.Client{} lc = &local.Client{}
st, err = lc.StatusWithoutPeers(ctx) st, err = lc.StatusWithoutPeers(ctx)
@ -110,6 +116,15 @@ func main() {
log.Fatalf("failed to listen on any of %v", st.TailscaleIPs) log.Fatalf("failed to listen on any of %v", st.TailscaleIPs)
} }
if flagDir == nil || *flagDir == "" {
// use user config directory as storage for tsidp oidc key
configDir, err := os.UserConfigDir()
if err != nil {
log.Fatalf("getting user config directory: %v", err)
}
rootPath = filepath.Join(configDir, "tsidp")
}
// tailscaled needs to be setting an HTTP header for funneled requests // tailscaled needs to be setting an HTTP header for funneled requests
// that older versions don't provide. // that older versions don't provide.
// TODO(naman): is this the correct check? // TODO(naman): is this the correct check?
@ -127,6 +142,8 @@ func main() {
Hostname: *flagHostname, Hostname: *flagHostname,
Dir: *flagDir, Dir: *flagDir,
} }
rootPath = ts.GetRootPath()
log.Printf("tsidp root path: %s", rootPath)
if *flagVerbose { if *flagVerbose {
ts.Logf = log.Printf ts.Logf = log.Printf
} }
@ -157,7 +174,9 @@ func main() {
lc: lc, lc: lc,
funnel: *flagFunnel, funnel: *flagFunnel,
localTSMode: *flagUseLocalTailscaled, localTSMode: *flagUseLocalTailscaled,
rootPath: rootPath,
} }
if *flagPort != 443 { if *flagPort != 443 {
srv.serverURL = fmt.Sprintf("https://%s:%d", strings.TrimSuffix(st.Self.DNSName, "."), *flagPort) srv.serverURL = fmt.Sprintf("https://%s:%d", strings.TrimSuffix(st.Self.DNSName, "."), *flagPort)
} else { } else {
@ -285,6 +304,7 @@ type idpServer struct {
serverURL string // "https://foo.bar.ts.net" serverURL string // "https://foo.bar.ts.net"
funnel bool funnel bool
localTSMode bool localTSMode bool
rootPath string // root path, used for storing state files
lazyMux lazy.SyncValue[*http.ServeMux] lazyMux lazy.SyncValue[*http.ServeMux]
lazySigningKey lazy.SyncValue[*signingKey] lazySigningKey lazy.SyncValue[*signingKey]
@ -819,8 +839,9 @@ func (s *idpServer) oidcSigner() (jose.Signer, error) {
func (s *idpServer) oidcPrivateKey() (*signingKey, error) { func (s *idpServer) oidcPrivateKey() (*signingKey, error) {
return s.lazySigningKey.GetErr(func() (*signingKey, error) { return s.lazySigningKey.GetErr(func() (*signingKey, error) {
keyPath := filepath.Join(s.rootPath, oidcKeyFile)
var sk signingKey var sk signingKey
b, err := os.ReadFile("oidc-key.json") b, err := os.ReadFile(keyPath)
if err == nil { if err == nil {
if err := sk.UnmarshalJSON(b); err == nil { if err := sk.UnmarshalJSON(b); err == nil {
return &sk, nil return &sk, nil
@ -835,7 +856,7 @@ func (s *idpServer) oidcPrivateKey() (*signingKey, error) {
if err != nil { if err != nil {
log.Fatalf("Error marshaling key: %v", err) log.Fatalf("Error marshaling key: %v", err)
} }
if err := os.WriteFile("oidc-key.json", b, 0600); err != nil { if err := os.WriteFile(keyPath, b, 0600); err != nil {
log.Fatalf("Error writing key: %v", err) log.Fatalf("Error writing key: %v", err)
} }
return &sk, nil return &sk, nil
@ -869,7 +890,6 @@ func (s *idpServer) serveJWKS(w http.ResponseWriter, r *http.Request) {
}); err != nil { }); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
return
} }
// openIDProviderMetadata is a partial representation of // openIDProviderMetadata is a partial representation of

View File

@ -1268,6 +1268,12 @@ func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, erro
return ln, nil return ln, nil
} }
// GetRootPath returns the root path of the tsnet server.
// This is where the state file and other data is stored.
func (s *Server) GetRootPath() string {
return s.rootPath
}
// CapturePcap can be called by the application code compiled with tsnet to save a pcap // CapturePcap can be called by the application code compiled with tsnet to save a pcap
// of packets which the netstack within tsnet sees. This is expected to be useful during // of packets which the netstack within tsnet sees. This is expected to be useful during
// debugging, probably not useful for production. // debugging, probably not useful for production.