Files
pmg/internal/cloudauth/resolver.go
T
f6e1d9e733 feat: authenticated Malysis analyzer with tenant exclusion support (#313)
* feat: authenticated Malysis analyzer with tenant exclusion support

When SafeDep Cloud credentials are available (keychain or environment),
PMG now uses an authenticated malware analysis query against
api.safedep.io instead of the unauthenticated community endpoint
(community-api.safedep.io). The API key and tenant ID are supplied via
the gRPC connection.

The authenticated response may carry a tenant-specific malicious package
exclusion. This is honored as an opt-in trust signal: a flagged package
is downgraded to allow only when a concrete exclusion (non-empty ID) is
present for the exact package version queried. Exclusions are never
honored for community queries and never weaken the verdict for packages
that were not flagged. Allowed-by-exclusion packages are surfaced as a
warning so the trust decision is never silent.

Changes are additive; non-authenticated usage is unchanged. Credential
resolution is extracted into internal/cloudauth and reused by both the
analyzer factory and the existing cloud sync client.

* fix: surface tenant exclusions in proxy mode; clarify comments

- Warn when proxy interceptor allows a flagged package due to a tenant
  exclusion, matching the guard flow so the trust decision is not silent.
- Remove stray doc comment above warnIfExcluded.
- Clarify that a verified-malware verdict can be downgraded by an
  exclusion in applyExclusion.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-01 15:32:22 +05:30

64 lines
2.1 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.
//
// An error is returned when no credentials are available, which callers can
// use to decide whether authenticated cloud features should be enabled.
func ResolveCredentials() (*cloud.Credentials, func() error, error) {
var resolvers []cloud.CredentialResolver
var keychainResolver cloud.CloseableCredentialResolver
keychainResolver, err := cloud.NewKeychainCredentialResolver(cloud.CredentialTypeAPIKey)
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
}