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

BREAKING: add support for k8s disable-node-collector flag #6311

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ trivy kubernetes [flags] [CONTEXT]
--cache-ttl duration cache TTL when using redis as cache backend
--clear-cache clear image caches without scanning
--compliance string compliance report to generate (k8s-nsa,k8s-cis,k8s-pss-baseline,k8s-pss-restricted)
--components strings specify which components to scan (workload,infra) (default [workload,infra])
--config-data strings specify paths from which data for the Rego policies will be recursively loaded
--config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files
--db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2")
--dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages
--disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node.
--download-db-only download/update vulnerability database but don't run a scan
--download-java-db-only download/update Java index database but don't run a scan
--exclude-kinds strings indicate the kinds exclude from scanning (example: node)
Expand Down
28 changes: 10 additions & 18 deletions pkg/flag/kubernetes_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,6 @@ var (
ConfigName: "kubernetes.kubeconfig",
Usage: "specify the kubeconfig file path to use",
}
ComponentsFlag = Flag[[]string]{
Name: "components",
ConfigName: "kubernetes.components",
Default: []string{
"workload",
"infra",
},
Values: []string{
"workload",
"infra",
},
Usage: "specify which components to scan",
}
K8sVersionFlag = Flag[string]{
Name: "k8s-version",
ConfigName: "kubernetes.k8s-version",
Expand All @@ -38,6 +25,11 @@ var (
ConfigName: "kubernetes.tolerations",
Usage: "specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule)",
}
DisableNodeCollector = Flag[bool]{
Name: "disable-node-collector",
ConfigName: "kubernetes.disableNodeCollector",
Usage: "When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node.",
}
NodeCollectorNamespace = Flag[string]{
Name: "node-collector-namespace",
ConfigName: "kubernetes.node-collector.namespace",
Expand Down Expand Up @@ -97,9 +89,9 @@ var (

type K8sFlagGroup struct {
KubeConfig *Flag[string]
Components *Flag[[]string]
K8sVersion *Flag[string]
Tolerations *Flag[[]string]
DisableNodeCollector *Flag[bool]
NodeCollectorImageRef *Flag[string]
NodeCollectorNamespace *Flag[string]
ExcludeOwned *Flag[bool]
Expand All @@ -114,12 +106,12 @@ type K8sFlagGroup struct {

type K8sOptions struct {
KubeConfig string
Components []string
K8sVersion string
Tolerations []corev1.Toleration
NodeCollectorImageRef string
NodeCollectorNamespace string
ExcludeOwned bool
DisableNodeCollector bool
ExcludeNodes map[string]string
ExcludeKinds []string
IncludeKinds []string
Expand All @@ -132,9 +124,9 @@ type K8sOptions struct {
func NewK8sFlagGroup() *K8sFlagGroup {
return &K8sFlagGroup{
KubeConfig: KubeConfigFlag.Clone(),
Components: ComponentsFlag.Clone(),
K8sVersion: K8sVersionFlag.Clone(),
Tolerations: TolerationsFlag.Clone(),
DisableNodeCollector: DisableNodeCollector.Clone(),
NodeCollectorNamespace: NodeCollectorNamespace.Clone(),
ExcludeOwned: ExcludeOwned.Clone(),
ExcludeNodes: ExcludeNodes.Clone(),
Expand All @@ -155,8 +147,8 @@ func (f *K8sFlagGroup) Name() string {
func (f *K8sFlagGroup) Flags() []Flagger {
return []Flagger{
f.KubeConfig,
f.Components,
f.K8sVersion,
f.DisableNodeCollector,
f.Tolerations,
f.NodeCollectorNamespace,
f.ExcludeOwned,
Expand Down Expand Up @@ -199,9 +191,9 @@ func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) {

return K8sOptions{
KubeConfig: f.KubeConfig.Value(),
Components: f.Components.Value(),
K8sVersion: f.K8sVersion.Value(),
Tolerations: tolerations,
DisableNodeCollector: f.DisableNodeCollector.Value(),
NodeCollectorNamespace: f.NodeCollectorNamespace.Value(),
ExcludeOwned: f.ExcludeOwned.Value(),
ExcludeNodes: exludeNodeLabels,
Expand Down
7 changes: 1 addition & 6 deletions pkg/flag/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,15 +360,10 @@ func (o *Options) Align() {
}

// Vulnerability scanning is disabled by default for CycloneDX.
if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) && len(o.K8sOptions.Components) == 0 { // remove K8sOptions.Components validation check when vuln scan is supported for k8s report with cycloneDX
if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) {
log.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`)
o.Scanners = nil
}

if o.Format == types.FormatCycloneDX && len(o.K8sOptions.Components) > 0 {
log.Info(`"k8s with --format cyclonedx" disable security scanning`)
o.Scanners = nil
}
}

// RegistryOpts returns options for OCI registries
Expand Down
8 changes: 6 additions & 2 deletions pkg/k8s/commands/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package commands
import (
"context"

"golang.org/x/exp/slices"
"golang.org/x/xerrors"

k8sArtifacts "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
)

Expand All @@ -34,7 +34,7 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err
trivyk8s.WithIncludeKinds(opts.IncludeKinds),
trivyk8s.WithExcludeOwned(opts.ExcludeOwned),
}
if opts.Scanners.AnyEnabled(types.MisconfigScanner) && slices.Contains(opts.Components, "infra") {
if opts.Scanners.AnyEnabled(types.MisconfigScanner) && !opts.DisableNodeCollector {
artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx,
trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace),
trivyk8s.WithIgnoreLabels(opts.ExcludeNodes),
Expand All @@ -53,6 +53,10 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err
return xerrors.Errorf(`unknown format %q. Use "json" or "table" or "cyclonedx"`, opts.Format)
}

if !opts.DisableNodeCollector && !opts.Quiet {
log.InfoContext(ctx, "Node scanning is enabled")
log.InfoContext(ctx, "If you want to disable Node scanning via an in-cluster Job, please try '--disable-node-collector' to disable the Node-Collector job.")
}
runner := newRunner(opts, cluster.GetCurrentContext())
return runner.run(ctx, artifacts)
}
1 change: 0 additions & 1 deletion pkg/k8s/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) er
Report: r.flagOpts.ReportFormat,
Output: output,
Severities: r.flagOpts.Severities,
Components: r.flagOpts.Components,
Scanners: r.flagOpts.ScanOptions.Scanners,
APIVersion: r.flagOpts.AppVersion,
}); err != nil {
Expand Down
28 changes: 12 additions & 16 deletions pkg/k8s/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ type Option struct {
Severities []dbTypes.Severity
ColumnHeading []string
Scanners types.Scanners
Components []string
APIVersion string
}

Expand Down Expand Up @@ -134,12 +133,12 @@ type reports struct {
// - misconfiguration report
// - rbac report
// - infra checks report
func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, components []string) []reports {
func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners) []reports {

var workloadMisconfig, infraMisconfig, rbacAssessment, workloadVulnerabilities, infraVulnerabilities, workloadResource []Resource
for _, resource := range k8sReport.Resources {
switch {
case vulnerabilitiesOrSecretResource(resource):
case vulnerabilitiesOrSecretResource(resource) && !infraResource(resource):
if resource.Namespace == infraNamespace || nodeInfoResource(resource) {
infraVulnerabilities = append(infraVulnerabilities, nodeKind(resource))
} else {
Expand All @@ -150,31 +149,29 @@ func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, compone
case infraResource(resource):
infraMisconfig = append(infraMisconfig, nodeKind(resource))
case scanners.Enabled(types.MisconfigScanner) &&
!rbacResource(resource) &&
slices.Contains(components, workloadComponent):
!rbacResource(resource):
workloadMisconfig = append(workloadMisconfig, resource)
}
}

var r []reports
workloadResource = append(workloadResource, workloadVulnerabilities...)
workloadResource = append(workloadResource, workloadMisconfig...)
if shouldAddToReport(scanners, components, workloadComponent) {
if shouldAddToReport(scanners) {
workloadReport := Report{
SchemaVersion: 0,
ClusterName: k8sReport.ClusterName,
Resources: workloadResource,
name: "Workload Assessment",
}
if slices.Contains(components, workloadComponent) {
r = append(r, reports{
Report: workloadReport,
Columns: WorkloadColumns(),
})
}
r = append(r, reports{
Report: workloadReport,
Columns: WorkloadColumns(),
})

}
infraMisconfig = append(infraMisconfig, infraVulnerabilities...)
if shouldAddToReport(scanners, components, infraComponent) {
if shouldAddToReport(scanners) {
r = append(r, reports{
Report: Report{
SchemaVersion: 0,
Expand Down Expand Up @@ -266,12 +263,11 @@ func (r Report) PrintErrors() {
}
}

func shouldAddToReport(scanners types.Scanners, components []string, componentType string) bool {
func shouldAddToReport(scanners types.Scanners) bool {
return scanners.AnyEnabled(
types.MisconfigScanner,
types.VulnerabilityScanner,
types.SecretScanner) &&
slices.Contains(components, componentType)
types.SecretScanner)
}

func vulnerabilitiesOrSecretResource(resource Resource) bool {
Expand Down
42 changes: 23 additions & 19 deletions pkg/k8s/report/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,6 @@ func Test_separateMisconfigReports(t *testing.T) {
name string
k8sReport Report
scanners types.Scanners
components []string
expectedReports []Report
}{
{
Expand All @@ -525,10 +524,6 @@ func Test_separateMisconfigReports(t *testing.T) {
types.MisconfigScanner,
types.RBACScanner,
},
components: []string{
workloadComponent,
infraComponent,
},
expectedReports: []Report{
// the order matter for the test
{
Expand All @@ -545,10 +540,6 @@ func Test_separateMisconfigReports(t *testing.T) {
name: "Config and Infra for the same resource",
k8sReport: k8sReport,
scanners: types.Scanners{types.MisconfigScanner},
components: []string{
workloadComponent,
infraComponent,
},
expectedReports: []Report{
// the order matter for the test
{
Expand All @@ -569,26 +560,39 @@ func Test_separateMisconfigReports(t *testing.T) {
},
},
{
name: "Config Report Only",
k8sReport: k8sReport,
scanners: types.Scanners{types.MisconfigScanner},
components: []string{workloadComponent},
name: "Config Report Only",
k8sReport: k8sReport,
scanners: types.Scanners{types.MisconfigScanner},
expectedReports: []Report{
{
Resources: []Resource{
{Kind: "Deployment"},
{Kind: "StatefulSet"},
},
},
{
Resources: []Resource{
{Kind: "Pod"},
},
},
},
},
{
name: "Infra Report Only",
k8sReport: k8sReport,
scanners: types.Scanners{types.MisconfigScanner},
components: []string{infraComponent},
name: "Infra Report Only",
k8sReport: k8sReport,
scanners: types.Scanners{types.MisconfigScanner},
expectedReports: []Report{
{Resources: []Resource{{Kind: "Pod"}}},
{
Resources: []Resource{
{Kind: "Deployment"},
{Kind: "StatefulSet"},
},
},
{
Resources: []Resource{
{Kind: "Pod"},
},
},
},
},

Expand All @@ -597,7 +601,7 @@ func Test_separateMisconfigReports(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners, tt.components)
reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners)
assert.Equal(t, len(tt.expectedReports), len(reports))

for i := range reports {
Expand Down
9 changes: 2 additions & 7 deletions pkg/k8s/report/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func NewSummaryWriter(output io.Writer, requiredSevs []dbTypes.Severity, columnH
}
}

func ColumnHeading(scanners types.Scanners, components, availableColumns []string) []string {
func ColumnHeading(scanners types.Scanners, availableColumns []string) []string {
columns := []string{
NamespaceColumn,
ResourceColumn,
Expand All @@ -47,12 +47,7 @@ func ColumnHeading(scanners types.Scanners, components, availableColumns []strin
case types.VulnerabilityScanner:
securityOptions[VulnerabilitiesColumn] = nil
case types.MisconfigScanner:
if slices.Contains(components, workloadComponent) {
securityOptions[MisconfigurationsColumn] = nil
}
if slices.Contains(components, infraComponent) {
securityOptions[MisconfigurationsColumn] = nil
}
securityOptions[MisconfigurationsColumn] = nil
case types.SecretScanner:
securityOptions[SecretsColumn] = nil
case types.RBACScanner:
Expand Down
Loading