From e296a6be8dcf2ad8f6a16a9e84afa11fd0546bec Mon Sep 17 00:00:00 2001 From: Mike O'Driscoll Date: Thu, 21 Aug 2025 13:56:11 -0400 Subject: [PATCH] cmd/tsidp: update oidc-funnel-clients.json store path (#16845) Update odic-funnel-clients.json to take a path, this allows setting the location of the file and prevents it from landing in the root directory or users home directory. Move setting of rootPath until after tsnet has started. Previously this was added for the lazy creation of the oidc-key.json. It's now needed earlier in the flow. Updates #16734 Fixes #16844 Signed-off-by: Mike O'Driscoll --- cmd/tsidp/tsidp.go | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/cmd/tsidp/tsidp.go b/cmd/tsidp/tsidp.go index e68e55ca9..2fc6d27e4 100644 --- a/cmd/tsidp/tsidp.go +++ b/cmd/tsidp/tsidp.go @@ -142,8 +142,6 @@ func main() { Hostname: *flagHostname, Dir: *flagDir, } - rootPath = ts.GetRootPath() - log.Printf("tsidp root path: %s", rootPath) if *flagVerbose { ts.Logf = log.Printf } @@ -168,6 +166,9 @@ func main() { log.Fatal(err) } lns = append(lns, ln) + + rootPath = ts.GetRootPath() + log.Printf("tsidp root path: %s", rootPath) } srv := &idpServer{ @@ -185,14 +186,18 @@ func main() { // Load funnel clients from disk if they exist, regardless of whether funnel is enabled // This ensures OIDC clients persist across restarts - f, err := os.Open(funnelClientsFile) + funnelClientsFilePath, err := getConfigFilePath(rootPath, funnelClientsFile) + if err != nil { + log.Fatalf("could not get funnel clients file path: %v", err) + } + f, err := os.Open(funnelClientsFilePath) if err == nil { if err := json.NewDecoder(f).Decode(&srv.funnelClients); err != nil { - log.Fatalf("could not parse %s: %v", funnelClientsFile, err) + log.Fatalf("could not parse %s: %v", funnelClientsFilePath, err) } f.Close() } else if !errors.Is(err, os.ErrNotExist) { - log.Fatalf("could not open %s: %v", funnelClientsFile, err) + log.Fatalf("could not open %s: %v", funnelClientsFilePath, err) } log.Printf("Running tsidp at %s ...", srv.serverURL) @@ -839,7 +844,10 @@ 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) + keyPath, err := getConfigFilePath(s.rootPath, oidcKeyFile) + if err != nil { + return nil, fmt.Errorf("could not get OIDC key file path: %w", err) + } var sk signingKey b, err := os.ReadFile(keyPath) if err == nil { @@ -1147,7 +1155,13 @@ func (s *idpServer) storeFunnelClientsLocked() error { if err := json.NewEncoder(&buf).Encode(s.funnelClients); err != nil { return err } - return os.WriteFile(funnelClientsFile, buf.Bytes(), 0600) + + funnelClientsFilePath, err := getConfigFilePath(s.rootPath, funnelClientsFile) + if err != nil { + return fmt.Errorf("storeFunnelClientsLocked: %v", err) + } + + return os.WriteFile(funnelClientsFilePath, buf.Bytes(), 0600) } const ( @@ -1260,3 +1274,18 @@ func isFunnelRequest(r *http.Request) bool { } return false } + +// getConfigFilePath returns the path to the config file for the given file name. +// The oidc-key.json and funnel-clients.json files were originally opened and written +// to without paths, and ended up in /root dir or home directory of the user running +// the process. To maintain backward compatibility, we return the naked file name if that +// file exists already, otherwise we return the full path in the rootPath. +func getConfigFilePath(rootPath string, fileName string) (string, error) { + if _, err := os.Stat(fileName); err == nil { + return fileName, nil + } else if errors.Is(err, os.ErrNotExist) { + return filepath.Join(rootPath, fileName), nil + } else { + return "", err + } +}