package cmd import ( "context" "fmt" "os" "time" gooidc "github.com/coreos/go-oidc/v3/oidc" "github.com/spf13/cobra" "golang.org/x/oauth2/clientcredentials" "guardianproject.dev/ops/nix-cache-login/internal/netrc" "guardianproject.dev/ops/nix-cache-login/internal/token" ) var serviceAccountCmd = &cobra.Command{ Use: "service-account", Short: "Authenticate using client credentials (for servers)", Long: `Authenticates using the OAuth2 client credentials flow for headless server environments. Requires client_secret in the config file. Exits 0 on success, 1 on failure. Designed to be called from a systemd timer.`, RunE: runServiceAccount, } func init() { rootCmd.AddCommand(serviceAccountCmd) } func runServiceAccount(cmd *cobra.Command, args []string) error { ctx := context.Background() if cfg.ClientSecret == "" { fmt.Fprintln(os.Stderr, "Error: client_secret is required in config for service-account mode.") os.Exit(1) } // OIDC discovery provider, err := gooidc.NewProvider(ctx, cfg.Issuer) if err != nil { fmt.Fprintf(os.Stderr, "OIDC discovery failed: %v\n", err) os.Exit(1) } ccCfg := clientcredentials.Config{ ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, TokenURL: provider.Endpoint().TokenURL, Scopes: []string{gooidc.ScopeOpenID}, } tok, err := ccCfg.Token(ctx) if err != nil { fmt.Fprintf(os.Stderr, "Failed to obtain token: %v\n", err) os.Exit(1) } // Write access token to netrc if err := netrc.Upsert(cfg.NetrcPath, cfg.CacheHost, tok.AccessToken); err != nil { fmt.Fprintf(os.Stderr, "Failed to update netrc: %v\n", err) os.Exit(1) } // Show expiry info claims, err := token.DecodePayload(tok.AccessToken) if err == nil { exp, remaining := token.ExpiryInfo(claims) if !exp.IsZero() { fmt.Fprintf(os.Stderr, "Token obtained. Valid until %s (%s remaining).\n", exp.Local().Format(time.RFC1123), remaining.Round(time.Minute)) } } else { fmt.Fprintln(os.Stderr, "Token obtained successfully.") } return nil }