2025-01-14 08:59:11 +00:00
|
|
|
package smb
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/user"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
2025-07-17 09:34:44 -04:00
|
|
|
"time"
|
2025-01-14 08:59:11 +00:00
|
|
|
|
|
|
|
|
"github.com/jcmturner/gokrb5/v8/client"
|
|
|
|
|
"github.com/jcmturner/gokrb5/v8/config"
|
|
|
|
|
"github.com/jcmturner/gokrb5/v8/credentials"
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-17 09:34:44 -04:00
|
|
|
// KerberosFactory encapsulates dependencies and caches for Kerberos clients.
|
|
|
|
|
type KerberosFactory struct {
|
|
|
|
|
// clientCache caches Kerberos clients keyed by resolved ccache path.
|
|
|
|
|
// Clients are reused unless the associated ccache file changes.
|
|
|
|
|
clientCache sync.Map // map[string]*client.Client
|
|
|
|
|
|
|
|
|
|
// errCache caches errors encountered when loading Kerberos clients.
|
|
|
|
|
// Prevents repeated attempts for paths that previously failed.
|
|
|
|
|
errCache sync.Map // map[string]error
|
|
|
|
|
|
|
|
|
|
// modTimeCache tracks the last known modification time of ccache files.
|
|
|
|
|
// Used to detect changes and trigger credential refresh.
|
|
|
|
|
modTimeCache sync.Map // map[string]time.Time
|
|
|
|
|
|
|
|
|
|
loadCCache func(string) (*credentials.CCache, error)
|
|
|
|
|
newClient func(*credentials.CCache, *config.Config, ...func(*client.Settings)) (*client.Client, error)
|
|
|
|
|
loadConfig func() (*config.Config, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewKerberosFactory creates a new instance of KerberosFactory with default dependencies.
|
|
|
|
|
func NewKerberosFactory() *KerberosFactory {
|
|
|
|
|
return &KerberosFactory{
|
|
|
|
|
loadCCache: credentials.LoadCCache,
|
|
|
|
|
newClient: client.NewFromCCache,
|
|
|
|
|
loadConfig: defaultLoadKerberosConfig,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetClient returns a cached Kerberos client or creates a new one if needed.
|
|
|
|
|
func (kf *KerberosFactory) GetClient(ccachePath string) (*client.Client, error) {
|
|
|
|
|
resolvedPath, err := resolveCcachePath(ccachePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stat, err := os.Stat(resolvedPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
kf.errCache.Store(resolvedPath, err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
mtime := stat.ModTime()
|
|
|
|
|
|
|
|
|
|
if oldMod, ok := kf.modTimeCache.Load(resolvedPath); ok {
|
|
|
|
|
if oldTime, ok := oldMod.(time.Time); ok && oldTime.Equal(mtime) {
|
|
|
|
|
if errVal, ok := kf.errCache.Load(resolvedPath); ok {
|
|
|
|
|
return nil, errVal.(error)
|
|
|
|
|
}
|
|
|
|
|
if clientVal, ok := kf.clientCache.Load(resolvedPath); ok {
|
|
|
|
|
return clientVal.(*client.Client), nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-14 08:59:11 +00:00
|
|
|
|
2025-07-17 09:34:44 -04:00
|
|
|
// Load Kerberos config
|
|
|
|
|
cfg, err := kf.loadConfig()
|
|
|
|
|
if err != nil {
|
|
|
|
|
kf.errCache.Store(resolvedPath, err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load ccache
|
|
|
|
|
ccache, err := kf.loadCCache(resolvedPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
kf.errCache.Store(resolvedPath, err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create new client
|
|
|
|
|
cl, err := kf.newClient(ccache, cfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
kf.errCache.Store(resolvedPath, err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cache and return
|
|
|
|
|
kf.clientCache.Store(resolvedPath, cl)
|
|
|
|
|
kf.errCache.Delete(resolvedPath)
|
|
|
|
|
kf.modTimeCache.Store(resolvedPath, mtime)
|
|
|
|
|
return cl, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// resolveCcachePath resolves the KRB5 ccache path.
|
2025-07-10 05:17:42 -04:00
|
|
|
func resolveCcachePath(ccachePath string) (string, error) {
|
|
|
|
|
if ccachePath == "" {
|
|
|
|
|
ccachePath = os.Getenv("KRB5CCNAME")
|
2025-01-14 08:59:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case strings.Contains(ccachePath, ":"):
|
|
|
|
|
parts := strings.SplitN(ccachePath, ":", 2)
|
2025-07-10 05:17:42 -04:00
|
|
|
prefix, path := parts[0], parts[1]
|
|
|
|
|
switch prefix {
|
2025-01-14 08:59:11 +00:00
|
|
|
case "FILE":
|
2025-07-10 05:17:42 -04:00
|
|
|
return path, nil
|
2025-01-14 08:59:11 +00:00
|
|
|
case "DIR":
|
2025-07-10 05:17:42 -04:00
|
|
|
primary, err := os.ReadFile(filepath.Join(path, "primary"))
|
2025-01-14 08:59:11 +00:00
|
|
|
if err != nil {
|
2025-07-10 05:17:42 -04:00
|
|
|
return "", err
|
2025-01-14 08:59:11 +00:00
|
|
|
}
|
2025-07-10 05:17:42 -04:00
|
|
|
return filepath.Join(path, strings.TrimSpace(string(primary))), nil
|
2025-01-14 08:59:11 +00:00
|
|
|
default:
|
2025-07-10 05:17:42 -04:00
|
|
|
return "", fmt.Errorf("unsupported KRB5CCNAME: %s", ccachePath)
|
2025-01-14 08:59:11 +00:00
|
|
|
}
|
|
|
|
|
case ccachePath == "":
|
|
|
|
|
u, err := user.Current()
|
|
|
|
|
if err != nil {
|
2025-07-10 05:17:42 -04:00
|
|
|
return "", err
|
2025-01-14 08:59:11 +00:00
|
|
|
}
|
2025-07-10 05:17:42 -04:00
|
|
|
return "/tmp/krb5cc_" + u.Uid, nil
|
|
|
|
|
default:
|
|
|
|
|
return ccachePath, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-14 08:59:11 +00:00
|
|
|
|
2025-07-17 09:34:44 -04:00
|
|
|
// defaultLoadKerberosConfig loads Kerberos config from default or env path.
|
|
|
|
|
func defaultLoadKerberosConfig() (*config.Config, error) {
|
2025-07-10 05:17:42 -04:00
|
|
|
cfgPath := os.Getenv("KRB5_CONFIG")
|
|
|
|
|
if cfgPath == "" {
|
|
|
|
|
cfgPath = "/etc/krb5.conf"
|
2025-01-14 08:59:11 +00:00
|
|
|
}
|
2025-07-10 05:17:42 -04:00
|
|
|
return config.Load(cfgPath)
|
|
|
|
|
}
|