diff --git a/cmd_credential.go b/cmd_credential.go index 85b75ae..79b87d8 100644 --- a/cmd_credential.go +++ b/cmd_credential.go @@ -10,6 +10,7 @@ import ( "os" "os/signal" "path/filepath" + "runtime" "strings" "syscall" "time" @@ -61,16 +62,31 @@ func credFetch(server, username string, noKerberos bool, logf func(string, ...an func credFetchKerberos(url string, logf func(string, ...any)) ([]byte, error) { krb5cfgPath := krb5ConfigPath() logf("Kerberos: loading config from %s", krb5cfgPath) - krb5cfg, err := config.Load(krb5cfgPath) - if err != nil { - return nil, fmt.Errorf("loading krb5 config: %w", err) + var krb5cfg *config.Config + if _, statErr := os.Stat(krb5cfgPath); os.IsNotExist(statErr) && os.Getenv("KRB5_CONFIG") == "" { + logf("Kerberos: %s not found, using default config (KDC discovery via DNS)", krb5cfgPath) + krb5cfg = config.New() + krb5cfg.LibDefaults.DNSLookupKDC = true + } else { + var err error + krb5cfg, err = config.Load(krb5cfgPath) + if err != nil { + return nil, fmt.Errorf("loading krb5 config: %w", err) + } } - ccPath := ccachePath() + ccPath, err := ccachePath() + if err != nil { + return nil, err + } logf("Kerberos: loading credential cache from %s", ccPath) ccache, err := credentials.LoadCCache(ccPath) if err != nil { - return nil, fmt.Errorf("no credential cache (%w) — run 'kinit'", err) + hint := "run 'kinit'" + if runtime.GOOS == "darwin" && os.Getenv("KRB5CCNAME") == "" { + hint = fmt.Sprintf("on macOS, run: kinit -c /tmp/krb5cc_%d", os.Getuid()) + } + return nil, fmt.Errorf("no credential cache (%w) — %s", err, hint) } cl, err := krb5client.NewFromCCache(ccache, krb5cfg, krb5client.DisablePAFXFAST(true)) @@ -224,15 +240,25 @@ func krb5ConfigPath() string { return cmp.Or(os.Getenv("KRB5_CONFIG"), "/etc/krb5.conf") } -// ccachePath returns the path to the active Kerberos credential cache. -// Respects $KRB5CCNAME; strips the "FILE:" prefix if present. -// Non-file ccache types (API:, KEYRING:, DIR:) are not supported by gokrb5 -// and will produce an error when LoadCCache is called. -func ccachePath() string { +// ccachePath returns the path to the active Kerberos credential cache, or an +// error if $KRB5CCNAME names a non-file cache type that gokrb5 cannot read. +// +// On macOS, kinit defaults to API: caches. Work around it with: +// +// kinit -c /tmp/krb5cc_$(id -u) +func ccachePath() (string, error) { if v := os.Getenv("KRB5CCNAME"); v != "" { - return strings.TrimPrefix(v, "FILE:") + for _, prefix := range []string{"API:", "KEYRING:", "DIR:", "KCM:"} { + if strings.HasPrefix(v, prefix) { + return "", fmt.Errorf( + "credential cache type %s is not supported (gokrb5 requires a file-based cache)\n"+ + "hint: re-run kinit with: kinit -c /tmp/krb5cc_%d", + prefix, os.Getuid()) + } + } + return strings.TrimPrefix(v, "FILE:"), nil } - return fmt.Sprintf("/tmp/krb5cc_%d", os.Getuid()) + return fmt.Sprintf("/tmp/krb5cc_%d", os.Getuid()), nil } // ── Password prompt ────────────────────────────────────────────────────────────