Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(misconf): Add fallback support for trivy-checks #8062

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

simar7
Copy link
Member

@simar7 simar7 commented Dec 6, 2024

Description

Trivy will now try to reach fallback sources before giving up and using embedded checks. Note if all sources fail to fetch, embedded checks are still present and will be fallen back on.

Before

trivy --debug config   /Users/repos/trivy-issues/5691-2
2024-12-06T01:32:42-07:00       DEBUG   Default config file "file_path=trivy.yaml" not found, using built in values
2024-12-06T01:32:42-07:00       DEBUG   Cache dir       dir="/Users/Library/Caches/trivy"
2024-12-06T01:32:42-07:00       DEBUG   Cache dir       dir="/Users/Library/Caches/trivy"
2024-12-06T01:32:42-07:00       DEBUG   Parsed severities       severities=[UNKNOWN LOW MEDIUM HIGH CRITICAL]
2024-12-06T01:32:42-07:00       INFO    [misconfig] Misconfiguration scanning is enabled
2024-12-06T01:32:42-07:00       DEBUG   [misconfig] Failed to open the check metadata   err="open /Users/simarpreetsingh/Library/Caches/trivy/policy/metadata.json: no such file or directory"
2024-12-06T01:32:42-07:00       INFO    [misconfig] Need to update the built-in checks
2024-12-06T01:32:42-07:00       INFO    [misconfig] Downloading the built-in checks...
2024-12-06T01:32:42-07:00       DEBUG   [misconfig] Loading check bundle        repo="rate-limited1.com/aquasec/trivy-checks:1"
2024-12-06T01:32:43-07:00       ERROR   [misconfig] Failed to download checks bundle    repo="rate-limited1.com/aquasec/trivy-checks:1" err="OCI repository error: 1 error occurred:\n\t* GET https://1ate-limited1.com/v2/: unexpected status code 429 TOOMANYREQUESTS"
2024-12-06T01:32:43-07:00       ERROR   [misconfig] Falling back to embedded checks  

After

trivy --debug config   /Users/repos/trivy-issues/5691-2
2024-12-06T01:22:51-07:00       DEBUG   Default config file "file_path=trivy.yaml" not found, using built in values
2024-12-06T01:22:51-07:00       DEBUG   Cache dir       dir="/Users/Library/Caches/trivy"
2024-12-06T01:22:51-07:00       DEBUG   Cache dir       dir="/Users/Library/Caches/trivy"
2024-12-06T01:22:51-07:00       DEBUG   Parsed severities       severities=[UNKNOWN LOW MEDIUM HIGH CRITICAL]
2024-12-06T01:22:51-07:00       INFO    [misconfig] Misconfiguration scanning is enabled
2024-12-06T01:22:51-07:00       DEBUG   [misconfig] Failed to open the check metadata   err="open /Users/Library/Caches/trivy/policy/metadata.json: no such file or directory"
2024-12-06T01:22:51-07:00       INFO    [misconfig] Need to update the built-in checks
2024-12-06T01:22:51-07:00       INFO    [misconfig] Downloading the built-in checks...
2024-12-06T01:22:51-07:00       DEBUG   [misconfig] Loading check bundle        repo="mirror.gcr.io/aquasec/trivy-checks:1"
2024-12-06T01:22:51-07:00       DEBUG   Credential error        err="docker-credential-gcr/helper: could not retrieve GCR's access token: google: could not find default credentials. See https://cloud.google.com/docs/authentication/external/set-up-adc for more information"
2024-12-06T01:22:51-07:00       ERROR   [misconfig] Failed to download checks bundle    repo="ffmirror.gcr.io/aquasec/trivy-checks:1" err="OCI repository error: 1 error occurred:\n\t* GET https://ffmirror.gcr.io/v2/: unexpected status code 404 Not Found: Invalid host name mirror.gcr.io.\n\n"
2024-12-06T01:22:51-07:00       DEBUG   [misconfig] Loading check bundle        repo="ghcr.io/aquasecurity/trivy-checks:1"
160.80 KiB / 160.80 KiB [------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% ? p/s 200ms
2024-12-06T01:22:52-07:00       DEBUG   [misconfig] Digest of the built-in checks       digest="sha256:27b408ac9a1d8d6a984bd98a2062792afa58d4534b8fa2dc46398cc6cd6a738a"
2024-12-06T01:22:52-07:00       DEBUG   [misconfig] Successfully loaded check bundle    repo="ghcr.io/aquasecurity/trivy-checks:1" digest="sha256:27b408ac9a1d8d6a984bd98a2062792afa58d4534b8fa2dc46398cc6cd6a738a"
2024-12-06T01:22:52-07:00       DEBUG   [misconfig] Checks successfully loaded from disk

Related issues

Checklist

  • I've read the guidelines for contributing to this repository.
  • I've followed the conventions in the PR title.
  • I've added tests that prove my fix is effective or that my feature works.
  • I've updated the documentation with the relevant information (if needed).
  • I've added usage information (if the PR introduces new options)
  • I've included a "before" and "after" example to the description (if the PR is a user interface change).

@simar7 simar7 requested a review from nikpivkin December 6, 2024 08:37
@simar7 simar7 self-assigned this Dec 6, 2024
oldart := c.artifact

for i, repo := range c.checkBundleRepos {
c.populateOCIArtifact(ctx, repo, registryOpts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work because the artifact is only initialized if it is missing, so the old one will be used. https://github.com/aquasecurity/trivy/blob/main/pkg/policy/policy.go#L92

Comment on lines +115 to +148
dst := c.contentDir()
if err := c.artifact.Download(ctx, dst, oci.DownloadOption{
MediaType: policyMediaType,
Quiet: c.quiet,
},
); err != nil {
if i == len(c.checkBundleRepos)-1 {
return xerrors.Errorf("download error: %w", err)
}
log.ErrorContext(ctx, "Failed to download checks bundle", log.String("repo", repo), log.Err(err))
c.artifact = oldart
continue
}

digest, err := c.artifact.Digest(ctx)
if err != nil {
if i == len(c.checkBundleRepos)-1 {
return xerrors.Errorf("digest error: %w", err)
}
log.ErrorContext(ctx, "Failed to get digest for check bundle", log.String("repo", repo), log.Err(err))
c.artifact = oldart
continue
}
log.DebugContext(ctx, "Digest of the built-in checks", log.String("digest", digest))

// Update metadata.json with the new digest and the current date
if err = c.updateMetadata(digest, c.clock.Now()); err != nil {
if i == len(c.checkBundleRepos)-1 {
return xerrors.Errorf("unable to update the check metadata: %w", err)
}
log.ErrorContext(ctx, "Failed to update metadata", log.String("digest", digest), log.Err(err))
c.artifact = oldart
continue
}
Copy link
Contributor

@nikpivkin nikpivkin Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artifact download can be extracted into a separate function to avoid multiple conditions:

func (c *Client) downloadArtifact(ctx context.Context) error {
	dst := c.contentDir()
	if err := c.artifact.Download(ctx, dst, oci.DownloadOption{
		MediaType: policyMediaType,
		Quiet:     c.quiet,
	},
	); err != nil {
		return xerrors.Errorf("download error: %w", err)
	}

	digest, err := c.artifact.Digest(ctx)
	if err != nil {
		return xerrors.Errorf("digest error: %w", err)
	}
	log.DebugContext(ctx, "Digest of the built-in checks", log.String("digest", digest))

	if err = c.updateMetadata(digest, c.clock.Now()); err != nil {
		return xerrors.Errorf("unable to update the check metadata: %w", err)
	}

	log.DebugContext(ctx, "Successfully loaded checks bundle", log.String("digest", digest))
	return nil
}

},
); err != nil {
if i == len(c.checkBundleRepos)-1 {
return xerrors.Errorf("download error: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we accumulate all the errors and in case the download fails from any source return it?

// Update metadata.json with the new digest and the current date
if err = c.updateMetadata(digest, c.clock.Now()); err != nil {
return xerrors.Errorf("unable to update the check metadata: %w", err)
oldart := c.artifact
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why fall back to a previous artifact?

@@ -162,7 +193,7 @@ func (c *Client) NeedsUpdate(ctx context.Context, registryOpts types.RegistryOpt
return false, nil
}

c.populateOCIArtifact(ctx, registryOpts)
c.populateOCIArtifact(ctx, "", registryOpts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The artifact for the default repository will be initialized here, even if the user has overridden the repositories.

@nikpivkin
Copy link
Contributor

@simar7 BTW, why do we need to check the digest match of the downloaded and remote bundles to determine if a download is needed, when we already use a timestamp to limit downloads to no more than once every 24 hours?

@knqyf263
Copy link
Collaborator

The update verification process differs between vulnerability database and check bundles:

  • Vulnerability Database:
    • Updated daily with new vulnerability information
    • After 24 hours, the database is guaranteed to have new content
    • Therefore, digest verification is unnecessary - if expired, it's definitely new
  • Check Bundles:
    • Updates are less often
    • Two-stage digest verification is beneficial:
      • Skip verification if recently downloaded (to reduce network calls)
      • Otherwise, check remote digest to avoid unnecessary downloads

However, we may choose to remove the digest verification logic for check bundles if:

  • We want to simplify the implementation
  • We believe trivy-checks are updated frequently enough

@nikpivkin
Copy link
Contributor

Then we need to improve digest checking for bundles from multiple repositories:
Consider the following situation:

  • We have two repositories A and B.
  • A bundle from repo A is stored in the cache.
  • On the next run, a request to A returns an error, we go to B and download the bundle again since the digest is different.
  • On the next run, we load the bundle from A because the digest is different again.

@knqyf263
Copy link
Collaborator

If I remember correctly, crane copy or oras copy keeps the digest. Can we see if we can use the same digsest in diffrent registries?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat(misconf): Add support for fallback for trivy-checks
3 participants