package auth import ( "context" "fmt" "os" "strings" "sync" "time" ) // ProjectedSATokenSource reads the kubelet-written projected // ServiceAccount token from a mounted Volume (typically // `/var/run/secrets/tokens/trust-api`) and re-reads it on each call so // kubelet's periodic refresh is picked up without a service restart. // // A short in-process cache avoids a syscall on every signing-service call; // kubelet refreshes projected tokens well before their `exp`, so a few // seconds of staleness is fine. Set CacheTTL negative to disable. type ProjectedSATokenSource struct { Path string CacheTTL time.Duration Now func() time.Time mu sync.Mutex cachedToken string cachedAt time.Time } // Token returns the current projected ServiceAccount token. func (p *ProjectedSATokenSource) Token(_ context.Context) (string, error) { if p == nil { return "", fmt.Errorf("auth.ProjectedSATokenSource: nil receiver") } if p.Path == "" { return "", fmt.Errorf("auth.ProjectedSATokenSource: Path is required") } p.mu.Lock() defer p.mu.Unlock() ttl := p.CacheTTL if ttl == 0 { ttl = 30 * time.Second } now := p.now() if ttl > 0 && p.cachedToken != "" && now.Sub(p.cachedAt) < ttl { return p.cachedToken, nil } raw, err := os.ReadFile(p.Path) if err != nil { return "", fmt.Errorf("read projected SA token %q: %w", p.Path, err) } tok := strings.TrimSpace(string(raw)) if tok == "" { return "", fmt.Errorf("projected SA token at %q is empty", p.Path) } p.cachedToken = tok p.cachedAt = now return tok, nil } func (p *ProjectedSATokenSource) now() time.Time { if p.Now != nil { return p.Now() } return time.Now() }