Skip to content

Commit

Permalink
feat(misconf): Add fallback support for trivy-checks
Browse files Browse the repository at this point in the history
Signed-off-by: Simar <[email protected]>
  • Loading branch information
simar7 committed Dec 6, 2024
1 parent ffe24e1 commit d3254ff
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 86 deletions.
2 changes: 1 addition & 1 deletion pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ func initMisconfScannerOption(ctx context.Context, opts flag.Options) (misconf.S
var downloadedPolicyPaths []string
var disableEmbedded bool

downloadedPolicyPaths, err := operation.InitBuiltinChecks(ctx, opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts())
downloadedPolicyPaths, err := operation.InitBuiltinChecks(ctx, opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepositories, opts.RegistryOpts())
if err != nil {
if !opts.SkipCheckUpdate {
log.ErrorContext(ctx, "Falling back to embedded checks", log.Err(err))
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/clean/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func cleanJavaDB(ctx context.Context, opts flag.Options) error {

func cleanCheckBundle(opts flag.Options) error {
log.Info("Removing check bundle...")
c, err := policy.NewClient(opts.CacheDir, true, opts.MisconfOptions.ChecksBundleRepository)
c, err := policy.NewClient(opts.CacheDir, true, opts.MisconfOptions.ChecksBundleRepositories)
if err != nil {
return xerrors.Errorf("failed to instantiate check client: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/commands/operation/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ func DownloadVEXRepositories(ctx context.Context, opts flag.Options) error {
}

// InitBuiltinChecks downloads the built-in policies and loads them
func InitBuiltinChecks(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) {
func InitBuiltinChecks(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepositories []string, registryOpts ftypes.RegistryOptions) ([]string, error) {
mu.Lock()
defer mu.Unlock()

client, err := policy.NewClient(cacheDir, quiet, checkBundleRepository)
client, err := policy.NewClient(cacheDir, quiet, checkBundleRepositories)
if err != nil {
return nil, xerrors.Errorf("check client error: %w", err)
}
Expand Down
64 changes: 33 additions & 31 deletions pkg/flag/misconf_flags.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package flag

import (
"fmt"

"github.com/samber/lo"

"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
Expand Down Expand Up @@ -82,17 +80,21 @@ var (
ConfigName: "misconfiguration.terraform.exclude-downloaded-modules",
Usage: "exclude misconfigurations for downloaded terraform modules",
}
ChecksBundleRepositoryFlag = Flag[string]{
Name: "checks-bundle-repository",
ConfigName: "misconfiguration.checks-bundle-repository",
Default: fmt.Sprintf("%s:%d", policy.BundleRepository, policy.BundleVersion),
Usage: "OCI registry URL to retrieve checks bundle from",
ChecksBundleRepositoriesFlag = Flag[[]string]{
Name: "checks-bundle-repositories",
ConfigName: "misconfiguration.checks-bundle-repositories",
Default: policy.BundleRepositories,
Usage: "OCI registry URL(s) to retrieve checks bundle from",
Aliases: []Alias{
{
Name: "policy-bundle-repository",
ConfigName: "misconfiguration.policy-bundle-repository",
Deprecated: true,
},
{
Name: "checks-bundle-repository",
ConfigName: "misconfiguration.checks-bundle-repository",
},
},
}
MisconfigScannersFlag = Flag[[]string]{
Expand All @@ -112,9 +114,9 @@ var (

// MisconfFlagGroup composes common printer flag structs used for commands providing misconfiguration scanning.
type MisconfFlagGroup struct {
IncludeNonFailures *Flag[bool]
ResetChecksBundle *Flag[bool]
ChecksBundleRepository *Flag[string]
IncludeNonFailures *Flag[bool]
ResetChecksBundle *Flag[bool]
ChecksBundleRepositories *Flag[[]string]

// Values Files
HelmValues *Flag[[]string]
Expand All @@ -131,9 +133,9 @@ type MisconfFlagGroup struct {
}

type MisconfOptions struct {
IncludeNonFailures bool
ResetChecksBundle bool
ChecksBundleRepository string
IncludeNonFailures bool
ResetChecksBundle bool
ChecksBundleRepositories []string

// Values Files
HelmValues []string
Expand All @@ -151,9 +153,9 @@ type MisconfOptions struct {

func NewMisconfFlagGroup() *MisconfFlagGroup {
return &MisconfFlagGroup{
IncludeNonFailures: IncludeNonFailuresFlag.Clone(),
ResetChecksBundle: ResetChecksBundleFlag.Clone(),
ChecksBundleRepository: ChecksBundleRepositoryFlag.Clone(),
IncludeNonFailures: IncludeNonFailuresFlag.Clone(),
ResetChecksBundle: ResetChecksBundleFlag.Clone(),
ChecksBundleRepositories: ChecksBundleRepositoriesFlag.Clone(),

HelmValues: HelmSetFlag.Clone(),
HelmFileValues: HelmSetFileFlag.Clone(),
Expand All @@ -177,7 +179,7 @@ func (f *MisconfFlagGroup) Flags() []Flagger {
return []Flagger{
f.IncludeNonFailures,
f.ResetChecksBundle,
f.ChecksBundleRepository,
f.ChecksBundleRepositories,
f.HelmValues,
f.HelmValueFiles,
f.HelmFileValues,
Expand All @@ -198,19 +200,19 @@ func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) {
}

return MisconfOptions{
IncludeNonFailures: f.IncludeNonFailures.Value(),
ResetChecksBundle: f.ResetChecksBundle.Value(),
ChecksBundleRepository: f.ChecksBundleRepository.Value(),
HelmValues: f.HelmValues.Value(),
HelmValueFiles: f.HelmValueFiles.Value(),
HelmFileValues: f.HelmFileValues.Value(),
HelmStringValues: f.HelmStringValues.Value(),
HelmAPIVersions: f.HelmAPIVersions.Value(),
HelmKubeVersion: f.HelmKubeVersion.Value(),
TerraformTFVars: f.TerraformTFVars.Value(),
CloudFormationParamVars: f.CloudformationParamVars.Value(),
TfExcludeDownloaded: f.TerraformExcludeDownloaded.Value(),
MisconfigScanners: xstrings.ToTSlice[analyzer.Type](f.MisconfigScanners.Value()),
ConfigFileSchemas: f.ConfigFileSchemas.Value(),
IncludeNonFailures: f.IncludeNonFailures.Value(),
ResetChecksBundle: f.ResetChecksBundle.Value(),
ChecksBundleRepositories: f.ChecksBundleRepositories.Value(),
HelmValues: f.HelmValues.Value(),
HelmValueFiles: f.HelmValueFiles.Value(),
HelmFileValues: f.HelmFileValues.Value(),
HelmStringValues: f.HelmStringValues.Value(),
HelmAPIVersions: f.HelmAPIVersions.Value(),
HelmKubeVersion: f.HelmKubeVersion.Value(),
TerraformTFVars: f.TerraformTFVars.Value(),
CloudFormationParamVars: f.CloudformationParamVars.Value(),
TfExcludeDownloaded: f.TerraformExcludeDownloaded.Value(),
MisconfigScanners: xstrings.ToTSlice[analyzer.Type](f.MisconfigScanners.Value()),
ConfigFileSchemas: f.ConfigFileSchemas.Value(),
}, nil
}
2 changes: 1 addition & 1 deletion pkg/k8s/commands/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func nodeCollectorOptions(ctx context.Context, opts flag.Options) []trivyk8s.Nod

ctx = log.WithContextPrefix(ctx, log.PrefixMisconfiguration)
contentPath, err := operation.InitBuiltinChecks(ctx, opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate,
opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts())
opts.MisconfOptions.ChecksBundleRepositories, opts.RegistryOpts())
if err != nil {
log.Error("Falling back to embedded checks", log.Err(err))
nodeCollectorOptions = append(nodeCollectorOptions,
Expand Down
109 changes: 70 additions & 39 deletions pkg/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ import (
)

const (
BundleVersion = 1 // Latest released MAJOR version for trivy-checks
BundleRepository = "mirror.gcr.io/aquasec/trivy-checks"
policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip"
updateInterval = 24 * time.Hour
BundleVersion = 1 // Latest released MAJOR version for trivy-checks
policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip"
updateInterval = 24 * time.Hour
)

var (
BundleRepositories = []string{
fmt.Sprintf("%s:%d", "mirror.gcr.io/aquasec/trivy-checks", BundleVersion), // primary
fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-checks", BundleVersion), // secondary
}
)

type options struct {
Expand Down Expand Up @@ -49,9 +55,9 @@ type Option func(*options)
// Client implements check operations
type Client struct {
*options
policyDir string
checkBundleRepo string
quiet bool
policyDir string
checkBundleRepos []string
quiet bool
}

// Metadata holds default check metadata
Expand All @@ -68,7 +74,7 @@ func (m Metadata) String() string {
}

// NewClient is the factory method for check client
func NewClient(cacheDir string, quiet bool, checkBundleRepo string, opts ...Option) (*Client, error) {
func NewClient(cacheDir string, quiet bool, checkBundleRepos []string, opts ...Option) (*Client, error) {
o := &options{
clock: clock.RealClock{},
}
Expand All @@ -77,47 +83,72 @@ func NewClient(cacheDir string, quiet bool, checkBundleRepo string, opts ...Opti
opt(o)
}

if checkBundleRepo == "" {
checkBundleRepo = fmt.Sprintf("%s:%d", BundleRepository, BundleVersion)
if len(checkBundleRepos) == 0 {
checkBundleRepos = BundleRepositories
}

return &Client{
options: o,
policyDir: filepath.Join(cacheDir, "policy"),
checkBundleRepo: checkBundleRepo,
quiet: quiet,
options: o,
policyDir: filepath.Join(cacheDir, "policy"),
checkBundleRepos: checkBundleRepos,
quiet: quiet,
}, nil
}

func (c *Client) populateOCIArtifact(ctx context.Context, registryOpts types.RegistryOptions) {
func (c *Client) populateOCIArtifact(ctx context.Context, repo string, registryOpts types.RegistryOptions) {
if c.artifact == nil {
log.DebugContext(ctx, "Loading check bundle", log.String("repository", c.checkBundleRepo))
c.artifact = oci.NewArtifact(c.checkBundleRepo, registryOpts)
if repo == "" {
repo = c.checkBundleRepos[0]
}
log.DebugContext(ctx, "Loading check bundle", log.String("repo", repo))
c.artifact = oci.NewArtifact(repo, registryOpts)
}
}

// DownloadBuiltinChecks download default policies from GitHub Pages
// DownloadBuiltinChecks download default policies from OCI registry
func (c *Client) DownloadBuiltinChecks(ctx context.Context, registryOpts types.RegistryOptions) error {
c.populateOCIArtifact(ctx, registryOpts)

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))

// 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

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

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
}

log.DebugContext(ctx, "Successfully loaded check bundle", log.String("repo", repo), log.String("digest", digest))
break
}

return nil
Expand Down Expand Up @@ -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)
digest, err := c.artifact.Digest(ctx)
if err != nil {
return false, xerrors.Errorf("digest error: %w", err)
Expand Down
Loading

0 comments on commit d3254ff

Please sign in to comment.