mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
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>
This commit is contained in:
@@ -9,15 +9,15 @@ import (
|
||||
"github.com/safedep/dry/cloud/endpointsync"
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/config"
|
||||
"github.com/safedep/pmg/internal/cloudauth"
|
||||
appVersion "github.com/safedep/pmg/internal/version"
|
||||
)
|
||||
|
||||
// SyncClientBundle holds a SyncClient and its underlying cloud client.
|
||||
// Callers must call Close() when done.
|
||||
type SyncClientBundle struct {
|
||||
syncClient *endpointsync.SyncClient
|
||||
cloudClient *cloud.Client
|
||||
keychainResolver cloud.CloseableCredentialResolver
|
||||
syncClient *endpointsync.SyncClient
|
||||
cloudClient *cloud.Client
|
||||
}
|
||||
|
||||
// Sync delivers pending events from the WAL to SafeDep Cloud.
|
||||
@@ -37,61 +37,26 @@ func (b *SyncClientBundle) Close() error {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if b.keychainResolver != nil {
|
||||
if err := b.keychainResolver.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// NewSyncClientBundle creates an authenticated SyncClient connected to SafeDep Cloud.
|
||||
func NewSyncClientBundle(cfg *config.RuntimeConfig) (*SyncClientBundle, error) {
|
||||
// Build credential resolver chain: keychain first, env fallback.
|
||||
var resolvers []cloud.CredentialResolver
|
||||
var keychainResolver cloud.CloseableCredentialResolver
|
||||
|
||||
keychainResolver, err := cloud.NewKeychainCredentialResolver(cloud.CredentialTypeAPIKey)
|
||||
// Resolve credentials via keychain-first, env fallback chain. The keychain
|
||||
// resolver can be closed immediately because the data plane client extracts
|
||||
// the credential values when it is created.
|
||||
creds, closeResolver, err := cloudauth.ResolveCredentials()
|
||||
if err != nil {
|
||||
log.Debugf("Keychain credential resolver not available, skipping: %v", err)
|
||||
} else {
|
||||
resolvers = append(resolvers, keychainResolver)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envResolver, err := cloud.NewEnvCredentialResolver()
|
||||
if err != nil {
|
||||
log.Debugf("Env credential resolver not available, skipping: %v", err)
|
||||
} else {
|
||||
resolvers = append(resolvers, envResolver)
|
||||
}
|
||||
|
||||
if len(resolvers) == 0 {
|
||||
if keychainResolver != nil {
|
||||
if closeErr := keychainResolver.Close(); closeErr != nil {
|
||||
log.Warnf("failed to close keychain resolver: %v", closeErr)
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := closeResolver(); closeErr != nil {
|
||||
log.Warnf("failed to close keychain resolver: %v", closeErr)
|
||||
}
|
||||
return nil, fmt.Errorf("no credential resolvers available")
|
||||
}
|
||||
|
||||
chain := cloud.NewChainCredentialResolver(resolvers...)
|
||||
creds, err := chain.Resolve()
|
||||
if err != nil {
|
||||
if keychainResolver != nil {
|
||||
if closeErr := keychainResolver.Close(); closeErr != nil {
|
||||
log.Warnf("failed to close keychain resolver: %v", closeErr)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to resolve cloud credentials: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
cloudClient, err := cloud.NewDataPlaneClient("pmg", creds)
|
||||
if err != nil {
|
||||
if keychainResolver != nil {
|
||||
if closeErr := keychainResolver.Close(); closeErr != nil {
|
||||
log.Warnf("failed to close keychain resolver: %v", closeErr)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to create data plane client: %w", err)
|
||||
}
|
||||
|
||||
@@ -115,17 +80,11 @@ func NewSyncClientBundle(cfg *config.RuntimeConfig) (*SyncClientBundle, error) {
|
||||
if closeErr := cloudClient.Close(); closeErr != nil {
|
||||
log.Warnf("failed to close cloud client after sync client init failure: %v", closeErr)
|
||||
}
|
||||
if keychainResolver != nil {
|
||||
if closeErr := keychainResolver.Close(); closeErr != nil {
|
||||
log.Warnf("failed to close keychain resolver: %v", closeErr)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to create sync client: %w", err)
|
||||
}
|
||||
|
||||
return &SyncClientBundle{
|
||||
syncClient: syncClient,
|
||||
cloudClient: cloudClient,
|
||||
keychainResolver: keychainResolver,
|
||||
syncClient: syncClient,
|
||||
cloudClient: cloudClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user