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"
"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

View File

@ -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.