Files
pmg/internal/cloudauth/resolver.go
T
Sahilb315 1cc1520d71 feat(cloud): support plaintext file credential store on systems without an OS keychain
› Tenant ID:  fails on headless Linux (containers, VMs, CI) because
no D-Bus Secret Service is available and the keychain resolver refuses
to construct. Add an explicit --insecure-file-store flag that enables
dry's plaintext file fallback (~/.config/safedep/creds.json, mode 0600)
for writing.

Reading is fallback-enabled unconditionally in the credential resolver
chain, logout and setup info, so stored file credentials resolve with
no extra flags and logout can always clear them. On systems with a
working keychain the file provider is never constructed.
2026-07-15 21:53:55 +05:30

71 lines
2.5 KiB
Go

// Package cloudauth provides shared helpers for resolving SafeDep Cloud
// credentials from the local keychain or environment. It centralizes the
// credential resolver chain used by both audit sync and authenticated
// analyzers so the resolution behavior stays consistent.
package cloudauth
import (
"fmt"
"github.com/safedep/dry/cloud"
"github.com/safedep/dry/log"
)
// ResolveCredentials resolves SafeDep Cloud API key credentials using a
// keychain-first, environment-fallback chain. It returns the resolved
// credentials and a close function that releases keychain resources. The
// close function is always non-nil and safe to call regardless of the error.
//
// The insecure file fallback is always enabled for reading so credentials
// stored via `pmg cloud login --insecure-file-store` on systems without an
// OS keychain (headless Linux, containers) resolve without extra flags. On
// systems with a working keychain the file provider is never constructed.
//
// An error is returned when no credentials are available, which callers can
// use to decide whether authenticated cloud features should be enabled.
func ResolveCredentials(opts ...cloud.KeychainOption) (*cloud.Credentials, func() error, error) {
var resolvers []cloud.CredentialResolver
var keychainResolver cloud.CloseableCredentialResolver
keychainOpts := append([]cloud.KeychainOption{cloud.WithInsecureFileFallback()}, opts...)
keychainResolver, err := cloud.NewKeychainCredentialResolver(cloud.CredentialTypeAPIKey, keychainOpts...)
if err != nil {
log.Debugf("Keychain credential resolver not available, skipping: %v", err)
} else {
resolvers = append(resolvers, keychainResolver)
}
envResolver, err := cloud.NewEnvCredentialResolver()
if err != nil {
log.Debugf("Env credential resolver not available, skipping: %v", err)
} else {
resolvers = append(resolvers, envResolver)
}
closeFn := func() error {
if keychainResolver != nil {
return keychainResolver.Close()
}
return nil
}
if len(resolvers) == 0 {
if err := closeFn(); err != nil {
log.Warnf("failed to close keychain resolver: %v", err)
}
return nil, func() error { return nil }, fmt.Errorf("no credential resolvers available")
}
chain := cloud.NewChainCredentialResolver(resolvers...)
creds, err := chain.Resolve()
if err != nil {
if closeErr := closeFn(); closeErr != nil {
log.Warnf("failed to close keychain resolver: %v", closeErr)
}
return nil, func() error { return nil }, fmt.Errorf("failed to resolve cloud credentials: %w", err)
}
return creds, closeFn, nil
}