78 lines
2 KiB
Go
78 lines
2 KiB
Go
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_file 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_file 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
|
|
}
|