diff --git a/cmd/tsidp/tsidp.go b/cmd/tsidp/tsidp.go index 8df68cd74..e68e55ca9 100644 --- a/cmd/tsidp/tsidp.go +++ b/cmd/tsidp/tsidp.go @@ -29,6 +29,7 @@ "net/url" "os" "os/signal" + "path/filepath" "strconv" "strings" "sync" @@ -60,6 +61,9 @@ type ctxConn struct{ // accessing the IDP over Funnel are persisted. const funnelClientsFile = "oidc-funnel-clients.json" +// oidcKeyFile is where the OIDC private key is persisted. +const oidcKeyFile = "oidc-key.json" + var ( flagVerbose = flag.Bool("verbose", false, "be verbose") flagPort = flag.Int("port", 443, "port to listen on") @@ -80,12 +84,14 @@ func main() { var ( lc *local.Client st *ipnstate.Status + rootPath string err error watcherChan chan error cleanup func() lns []net.Listener ) + if *flagUseLocalTailscaled { lc = &local.Client{} st, err = lc.StatusWithoutPeers(ctx) @@ -110,6 +116,15 @@ func main() { 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 // that older versions don't provide. // TODO(naman): is this the correct check? @@ -127,6 +142,8 @@ func main() { Hostname: *flagHostname, Dir: *flagDir, } + rootPath = ts.GetRootPath() + log.Printf("tsidp root path: %s", rootPath) if *flagVerbose { ts.Logf = log.Printf } @@ -157,7 +174,9 @@ func main() { lc: lc, funnel: *flagFunnel, localTSMode: *flagUseLocalTailscaled, + rootPath: rootPath, } + if *flagPort != 443 { srv.serverURL = fmt.Sprintf("https://%s:%d", strings.TrimSuffix(st.Self.DNSName, "."), *flagPort) } else { @@ -285,6 +304,7 @@ type idpServer struct { serverURL string // "https://foo.bar.ts.net" funnel bool localTSMode bool + rootPath string // root path, used for storing state files lazyMux lazy.SyncValue[*http.ServeMux] lazySigningKey lazy.SyncValue[*signingKey] @@ -819,8 +839,9 @@ func (s *idpServer) oidcSigner() (jose.Signer, error) { func (s *idpServer) oidcPrivateKey() (*signingKey, error) { return s.lazySigningKey.GetErr(func() (*signingKey, error) { + keyPath := filepath.Join(s.rootPath, oidcKeyFile) var sk signingKey - b, err := os.ReadFile("oidc-key.json") + b, err := os.ReadFile(keyPath) if err == nil { if err := sk.UnmarshalJSON(b); err == nil { return &sk, nil @@ -835,7 +856,7 @@ func (s *idpServer) oidcPrivateKey() (*signingKey, error) { if err != nil { 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) } return &sk, nil @@ -869,7 +890,6 @@ func (s *idpServer) serveJWKS(w http.ResponseWriter, r *http.Request) { }); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } - return } // openIDProviderMetadata is a partial representation of diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index d81dec7d6..2715917a2 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -1268,6 +1268,12 @@ func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, erro 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 // of packets which the netstack within tsnet sees. This is expected to be useful during // debugging, probably not useful for production.