Last active
          September 3, 2025 17:47 
        
      - 
      
- 
        Save mostlygeek/558b83f12383a0fa9a6d89c5542b89bd to your computer and use it in GitHub Desktop. 
Revisions
- 
        mostlygeek revised this gist Sep 3, 2025 . 1 changed file with 10 additions and 6 deletions.There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -15,18 +15,22 @@ import ( "syscall" "time" // Existing Tailscale dependencies "tailscale.com/client/local" "tailscale.com/envknob" "tailscale.com/hostinfo" "tailscale.com/ipn/ipnstate" "tailscale.com/tsnet" "tailscale.com/version" // reload idpServer into app "tailscale.com/cmd/tsidp/internal/app" // http handlers "tailscale.com/cmd/tsidp/internal/handlers" // CRUD on the .json data files "tailscale.com/cmd/tsidp/internal/store" ) // FunnelClientsFile is the file where client IDs and secrets are persisted. 
- 
        mostlygeek created this gist Sep 3, 2025 .There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,222 @@ // The tsidp command is an OpenID Connect Identity Provider server. package main import ( "context" "errors" "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "strings" "syscall" "time" // New internal packages containing the refactored logic "tailscale.com/cmd/tsidp/internal/app" "tailscale.com/cmd/tsidp/internal/handlers" "tailscale.com/cmd/tsidp/internal/store" // Existing Tailscale dependencies "tailscale.com/client/local" "tailscale.com/envknob" "tailscale.com/hostinfo" "tailscale.com/ipn/ipnstate" "tailscale.com/tsnet" "tailscale.com/version" ) // FunnelClientsFile is the file where client IDs and secrets are persisted. const FunnelClientsFile = "oidc-funnel-clients.json" // Config holds all configuration for the tsidp application. type Config struct { Verbose bool Port int LocalPort int UseLocalTailscaled bool Funnel bool Hostname string Dir string EnableSTS bool ServerURL string // Populated at runtime LoopbackURL string // Populated at runtime } // parseFlags parses command-line flags and returns a Config struct. func parseFlags() *Config { cfg := &Config{} flag.BoolVar(&cfg.Verbose, "verbose", false, "be verbose") flag.IntVar(&cfg.Port, "port", 443, "port to listen on") flag.IntVar(&cfg.LocalPort, "local-port", -1, "allow requests from localhost") flag.BoolVar(&cfg.UseLocalTailscaled, "use-local-tailscaled", false, "use local tailscaled instead of tsnet") flag.BoolVar(&cfg.Funnel, "funnel", false, "use Tailscale Funnel to make tsidp available on the public internet") flag.StringVar(&cfg.Hostname, "hostname", "idp", "tsnet hostname to use instead of idp") flag.StringVar(&cfg.Dir, "dir", "", "tsnet state directory") flag.BoolVar(&cfg.EnableSTS, "enable-sts", false, "enable OIDC STS token exchange support") flag.Parse() if cfg.LocalPort != -1 { cfg.LoopbackURL = fmt.Sprintf("http://localhost:%d", cfg.LocalPort) } return cfg } func main() { // 1. Parse configuration from command-line flags. cfg := parseFlags() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() if !envknob.UseWIPCode() { log.Fatal("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.") } // 2. Set up Tailscale client and get network status. lc, st, cleanup, err := setupTailscale(ctx, cfg) if err != nil { log.Fatalf("failed to set up tailscale: %v", err) } defer cleanup() // Update config with the runtime server URL. if cfg.Port != 443 { cfg.ServerURL = fmt.Sprintf("https://%s:%d", strings.TrimSuffix(st.Self.DNSName, "."), cfg.Port) } else { cfg.ServerURL = fmt.Sprintf("https://%s", strings.TrimSuffix(st.Self.DNSName, ".")) } log.Printf("tsidp base URL will be %s", cfg.ServerURL) // 3. Initialize dependencies. clientStore, err := store.NewFileStore(FunnelClientsFile) if err != nil { log.Fatalf("failed to initialize client store: %v", err) } // 4. Instantiate the core application server. idpServer, err := app.NewServer(cfg, lc, clientStore) if err != nil { log.Fatalf("failed to create IDP server: %v", err) } // 5. Set up the HTTP router with all the handlers. router := handlers.NewRouter(idpServer) // 6. Configure and start the HTTP server. httpServer := &http.Server{ Handler: router, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } // Start network listeners. listeners, err := setupListeners(ctx, cfg, lc, st) if err != nil { log.Fatalf("failed to set up listeners: %v", err) } // Run servers in the background. errChan := make(chan error, len(listeners)) for _, ln := range listeners { log.Printf("Listening on %s", ln.Addr()) go func(l net.Listener) { if err := httpServer.Serve(l); !errors.Is(err, http.ErrServerClosed) { errChan <- err } }(l) } log.Printf("tsidp server started successfully.") // Wait for a shutdown signal or a server error. select { case err := <-errChan: log.Fatalf("server error: %v", err) case <-ctx.Done(): log.Println("shutdown signal received, stopping server...") } // Perform a graceful shutdown. shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { log.Printf("HTTP server shutdown error: %v", err) } log.Println("server stopped gracefully.") } // setupTailscale initializes the Tailscale connection. func setupTailscale(ctx context.Context, cfg *Config) (*local.Client, *ipnstate.Status, func(), error) { if cfg.UseLocalTailscaled { lc := &local.Client{} st, err := lc.StatusWithoutPeers(ctx) if err != nil { return nil, nil, func() {}, fmt.Errorf("getting status: %w", err) } return lc, st, func() {}, nil } hostinfo.SetApp("tsidp") ts := &tsnet.Server{ Hostname: cfg.Hostname, Dir: cfg.Dir, } if cfg.Verbose { ts.Logf = log.Printf } st, err := ts.Up(ctx) if err != nil { return nil, nil, func() {}, fmt.Errorf("starting tsnet.Server: %w", err) } lc, err := ts.LocalClient() if err != nil { ts.Close() return nil, nil, func() {}, fmt.Errorf("getting local client: %w", err) } return lc, st, func() { ts.Close() }, nil } // setupListeners configures and returns the required network listeners. func setupListeners(ctx context.Context, cfg *Config, lc *local.Client, st *ipnstate.Status) ([]net.Listener, error) { var lns []net.Listener if !cfg.UseLocalTailscaled { ts, ok := lc.TsnetServer() if !ok { return nil, errors.New("cannot get tsnet server from local client") } var ln net.Listener var err error if cfg.Funnel { ln, err = ts.ListenFunnel("tcp", fmt.Sprintf(":%d", cfg.Port)) } else { ln, err = ts.ListenTLS("tcp", fmt.Sprintf(":%d", cfg.Port)) } if err != nil { return nil, err } lns = append(lns, ln) } else { // Logic for local tailscaled listeners if version.AtLeast(st.Version, "1.71.0") { // Setup serve config... } } if cfg.LocalPort > 0 { ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", cfg.LocalPort)) if err != nil { return nil, err } lns = append(lns, ln) } if len(lns) == 0 { return nil, errors.New("no listeners were successfully started") } return lns, nil }