Skip to content

Commit

Permalink
fix: support affinity for scan jobs (#1915)
Browse files Browse the repository at this point in the history
* fix: support affinity for scan jobs

* Minor fixes

* Re-generate

* Undo static manifest changes
  • Loading branch information
maxbrunet committed Mar 19, 2024
1 parent b7248f2 commit d62c500
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 6 deletions.
1 change: 1 addition & 0 deletions deploy/helm/README.md
Expand Up @@ -177,6 +177,7 @@ Keeps security report resources updated
| trivyOperator.policiesConfig | string | `""` | policiesConfig Custom Rego Policies to be used by the config audit scanner See https://github.com/aquasecurity/trivy-operator/blob/main/docs/tutorials/writing-custom-configuration-audit-policies.md for more details. |
| trivyOperator.reportRecordFailedChecksOnly | bool | `true` | reportRecordFailedChecksOnly flag is to record only failed checks on misconfiguration reports (config-audit and rbac assessment) |
| trivyOperator.reportResourceLabels | string | `""` | reportResourceLabels comma-separated scanned resource labels which the user wants to include in the Prometheus metrics report. Example: `owner,app` |
| trivyOperator.scanJobAffinity | list | `[]` | scanJobAffinity affinity to be applied to the scanner pods and node-collector |
| trivyOperator.scanJobAnnotations | string | `""` | scanJobAnnotations comma-separated representation of the annotations which the user wants the scanner pods to be annotated with. Example: `foo=bar,env=stage` will annotate the scanner pods with the annotations `foo: bar` and `env: stage` |
| trivyOperator.scanJobAutomountServiceAccountToken | bool | `false` | scanJobAutomountServiceAccountToken the flag to enable automount for service account token on scan job |
| trivyOperator.scanJobCompressLogs | bool | `true` | scanJobCompressLogs control whether scanjob output should be compressed or plain |
Expand Down
5 changes: 4 additions & 1 deletion deploy/helm/templates/configmaps/operator.yaml
Expand Up @@ -6,6 +6,9 @@ metadata:
namespace: {{ include "trivy-operator.namespace" . }}
labels: {{- include "trivy-operator.labels" . | nindent 4 }}
data:
{{- with .Values.trivyOperator.scanJobAffinity }}
scanJob.affinity: {{ . | toJson | quote }}
{{- end }}
{{- with .Values.trivyOperator.scanJobTolerations }}
scanJob.tolerations: {{ . | toJson | quote }}
{{- end }}
Expand Down Expand Up @@ -75,4 +78,4 @@ data:
{{- with .Values.nodeCollector.imagePullSecret }}
node.collector.imagePullSecret: "{{ . }}"
{{- end }}
node.collector.nodeSelector: {{ .Values.nodeCollector.useNodeSelector | quote }}
node.collector.nodeSelector: {{ .Values.nodeCollector.useNodeSelector | quote }}
2 changes: 2 additions & 0 deletions deploy/helm/values.yaml
Expand Up @@ -216,6 +216,8 @@ trivyOperator:
configAuditReportsPlugin: "Trivy"
# -- scanJobCompressLogs control whether scanjob output should be compressed or plain
scanJobCompressLogs: true
# -- scanJobAffinity affinity to be applied to the scanner pods and node-collector
scanJobAffinity: []
# -- scanJobTolerations tolerations to be applied to the scanner pods and node-collector so that they can run on nodes with matching taints
scanJobTolerations: []
# -- If you do want to specify tolerations, uncomment the following lines, adjust them as necessary, and remove the
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/installation/configuration.md
Expand Up @@ -69,6 +69,7 @@ To change the target namespace from all namespaces to the `default` namespace ed
|---|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `vulnerabilityReports.scanner`| `Trivy`| The name of the plugin that generates vulnerability reports. Either `Trivy` or `Aqua`. |
| `vulnerabilityReports.scanJobsInSameNamespace` | `"false"`| Whether to run vulnerability scan jobs in same namespace of workload. Set `"true"` to enable. |
| `scanJob.affinity` | N/A| JSON representation of the [affinity] to be applied to the scanner pods and node-collector. Example: `'{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]},{"matchExpressions":[{"key":"virtual-kubelet.io/provider","operator":"DoesNotExist"}]}]}}}'`
| `scanJob.tolerations`| N/A| JSON representation of the [tolerations] to be applied to the scanner pods and node-collector so that they can run on nodes with matching taints. Example: `'[{"key":"key1", "operator":"Equal", "value":"value1", "effect":"NoSchedule"}]'`
| `nodeCollector.volumeMounts`| see helm/values.yaml | node-collector pod volumeMounts definition for collecting config files information
| `nodeCollector.volumes`| see helm/values.yaml | node-collector pod volumes definition for collecting config files information |
Expand Down Expand Up @@ -132,6 +133,7 @@ kubectl patch cm trivy-operator-trivy-config -n trivy-system \
-p '[{"op": "remove", "path": "/data/trivy.httpProxy"}]'
```

[affinity]: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity
[tolerations]: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration

[prometheus]: https://github.com/prometheus
1 change: 1 addition & 0 deletions docs/settings.md
Expand Up @@ -55,6 +55,7 @@ configuration settings for common use cases. For example, switch Trivy from [Sta
| `vulnerabilityReports.scanner` | `Trivy` | The name of the plugin that generates vulnerability reports. Either `Trivy` or `Aqua`. |
| `vulnerabilityReports.scanJobsInSameNamespace` | `"false"` | Whether to run vulnerability scan jobs in same namespace of workload. Set `"true"` to enable. |
| `configAuditReports.scanner` | `Trivy` | The name of the plugin that generates config audit reports. |
| `scanJob.affinity` | N/A | JSON representation of the [affinity] to be applied to the scanner pods and node-collector. Example: `'{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]},{"matchExpressions":[{"key":"virtual-kubelet.io/provider","operator":"DoesNotExist"}]}]}}}'` |
| `scanJob.tolerations` | N/A | JSON representation of the [tolerations] to be applied to the scanner pods and node-collector so that they can run on nodes with matching taints. Example: `'[{"key":"key1", "operator":"Equal", "value":"value1", "effect":"NoSchedule"}]'` |
| `nodeCollector.volumeMounts`| see helm/values.yaml | node-collector pod volumeMounts definition for collecting config files information
| `nodeCollector.volumes`| see helm/values.yaml | node-collector pod volumes definition for collecting config files information
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -6,7 +6,7 @@ require (
github.com/CycloneDX/cyclonedx-go v0.8.0
github.com/aquasecurity/defsec v0.94.1
github.com/aquasecurity/trivy v0.49.1
github.com/aquasecurity/trivy-kubernetes v0.6.4-0.20240129122346-af2de58a7466
github.com/aquasecurity/trivy-kubernetes v0.6.4-0.20240311135805-8e927abd008d
github.com/bluele/gcache v0.0.2
github.com/caarlos0/env/v6 v6.10.1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -312,8 +312,8 @@ github.com/aquasecurity/trivy-iac v0.8.0 h1:NKFhk/BTwQ0jIh4t74V8+6UIGUvPlaxO9HPl
github.com/aquasecurity/trivy-iac v0.8.0/go.mod h1:ARiMeNqcaVWOXJmp8hmtMnNm/Jd836IOmDBUW5r4KEk=
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI=
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
github.com/aquasecurity/trivy-kubernetes v0.6.4-0.20240129122346-af2de58a7466 h1:pM/cncKgNyvCzhOiFL19F4pyebktYBwl9/XyQl44OCI=
github.com/aquasecurity/trivy-kubernetes v0.6.4-0.20240129122346-af2de58a7466/go.mod h1:pGWup/B9igRLCuJ0fmI7hft4Ni7l5zCspwtt5dvVNTY=
github.com/aquasecurity/trivy-kubernetes v0.6.4-0.20240311135805-8e927abd008d h1:BWcT4YE8jHMWMIxZCC6sgriRP4xLTOyEe8tFoY4pnXQ=
github.com/aquasecurity/trivy-kubernetes v0.6.4-0.20240311135805-8e927abd008d/go.mod h1:6T6aM0FnBMoNfLiPFPatPXfBpMO9Zgo/G22A5KWGijc=
github.com/aquasecurity/trivy-policies v0.8.0 h1:LvmIdw/DfTF72Lc8L+CKLYzfb5BFYzLBGFFR95PKC74=
github.com/aquasecurity/trivy-policies v0.8.0/go.mod h1:qF/t59pgK/0JTV6tXaeA3Iw3opzoMgzGCDcTDBmqb30=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
Expand Down
5 changes: 5 additions & 0 deletions pkg/configauditreport/controller/node.go
Expand Up @@ -121,6 +121,10 @@ func (r *NodeReconciler) reconcileNodes() reconcile.Func {
if err != nil {
return ctrl.Result{}, fmt.Errorf("preparing job: %w", err)
}
jobAffinity, err := r.GetScanJobAffinity()
if err != nil {
return ctrl.Result{}, fmt.Errorf("getting job affinity: %w", err)
}
jobTolerations, err := r.GetScanJobTolerations()
if err != nil {
return ctrl.Result{}, fmt.Errorf("getting job tolerations: %w", err)
Expand Down Expand Up @@ -168,6 +172,7 @@ func (r *NodeReconciler) reconcileNodes() reconcile.Func {
j.WithJobNamespace(on),
j.WithServiceAccount(r.ServiceAccount),
j.WithCollectorTimeout(r.Config.ScanJobTimeout),
j.WithJobAffinity(jobAffinity),
j.WithJobTolerations(jobTolerations),
j.WithPodSpecSecurityContext(scanJobSecurityContext),
j.WithContainerSecurityContext(scanJobContainerSecurityContext),
Expand Down
17 changes: 16 additions & 1 deletion pkg/trivyoperator/config.go
Expand Up @@ -59,6 +59,7 @@ const (
keyVulnerabilityReportsScanner = "vulnerabilityReports.scanner"
KeyVulnerabilityScansInSameNamespace = "vulnerabilityReports.scanJobsInSameNamespace"
keyConfigAuditReportsScanner = "configAuditReports.scanner"
keyScanJobAffinity = "scanJob.affinity"
keyScanJobTolerations = "scanJob.tolerations"
KeyScanJobcompressLogs = "scanJob.compressLogs"
KeyNodeCollectorVolumes = "nodeCollector.volumes"
Expand Down Expand Up @@ -168,6 +169,20 @@ func (c ConfigData) GetConfigAuditReportsScanner() Scanner {
return Scanner(value)
}

func (c ConfigData) GetScanJobAffinity() (*corev1.Affinity, error) {
if c[keyScanJobAffinity] == "" {
return nil, nil
}

scanJobAffinity := &corev1.Affinity{}
err := json.Unmarshal([]byte(c[keyScanJobAffinity]), scanJobAffinity)
if err != nil {
return nil, fmt.Errorf("failed parsing incorrectly formatted custom scan pod template affinity: %s", c[keyScanJobAffinity])
}

return scanJobAffinity, nil
}

func (c ConfigData) GetScanJobTolerations() ([]corev1.Toleration, error) {
var scanJobTolerations []corev1.Toleration
if c[keyScanJobTolerations] == "" {
Expand Down Expand Up @@ -219,7 +234,7 @@ func (c ConfigData) GetScanJobNodeSelector() (map[string]string, error) {
}

if err := json.Unmarshal([]byte(c[keyScanJobNodeSelector]), &scanJobNodeSelector); err != nil {
return scanJobNodeSelector, fmt.Errorf("failed to parse incorrect job template nodeSelector %s: %w", c[keyScanJobNodeSelector], err)
return scanJobNodeSelector, fmt.Errorf("failed to parse incorrect pod template nodeSelector %s: %w", c[keyScanJobNodeSelector], err)
}

return scanJobNodeSelector, nil
Expand Down
71 changes: 70 additions & 1 deletion pkg/trivyoperator/config_test.go
Expand Up @@ -76,6 +76,75 @@ func TestConfigData_GetConfigAuditReportsScanner(t *testing.T) {
}
}

func TestConfigData_GetScanJobAffinity(t *testing.T) {
testCases := []struct {
name string
config trivyoperator.ConfigData
expected *corev1.Affinity
expectError string
}{
{
name: "no scanJob.affinity in ConfigData",
config: trivyoperator.ConfigData{},
expected: nil,
},
{
name: "scanJob.affinity value is not json",
config: trivyoperator.ConfigData{"scanJob.affinity": `lolwut`},
expected: nil,
expectError: "invalid character 'l' looking for beginning of value",
},
{
name: "empty JSON array",
config: trivyoperator.ConfigData{"scanJob.affinity": `{}`},
expected: &corev1.Affinity{},
},
{
name: "valid affinity",
config: trivyoperator.ConfigData{
"scanJob.affinity": `{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]},{"matchExpressions":[{"key":"virtual-kubelet.io/provider","operator":"DoesNotExist"}]}]}}}`,
},
expected: &corev1.Affinity{
NodeAffinity: &corev1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
NodeSelectorTerms: []corev1.NodeSelectorTerm{
{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"linux"},
},
},
},
{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "virtual-kubelet.io/provider",
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
},
},
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.config.GetScanJobAffinity()
if tc.expectError != "" {
assert.Error(t, err, "unexpected end of JSON input", tc.name)
} else {
assert.NoError(t, err, tc.name)
}
assert.Equal(t, tc.expected, got, tc.name)
})
}
}

func TestConfigData_GetScanJobTolerations(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -429,7 +498,7 @@ func TestConfigData_GetScanJobNodeSelector(t *testing.T) {
"scanJob.nodeSelector": "{dlzm",
},
expected: map[string]string{},
expectError: "failed to parse incorrect job template nodeSelector {dlzm: invalid character 'd' looking for beginning of object key string",
expectError: "failed to parse incorrect pod template nodeSelector {dlzm: invalid character 'd' looking for beginning of object key string",
},
}

Expand Down
10 changes: 10 additions & 0 deletions pkg/vulnerabilityreport/builder.go
Expand Up @@ -29,6 +29,7 @@ type ScanJobBuilder struct {
timeout time.Duration
object client.Object
credentials map[string]docker.Auth
affinity *corev1.Affinity
tolerations []corev1.Toleration
nodeSelector map[string]string
annotations map[string]string
Expand Down Expand Up @@ -79,6 +80,11 @@ func (s *ScanJobBuilder) WithObject(object client.Object) *ScanJobBuilder {
return s
}

func (s *ScanJobBuilder) WithAffinity(affinity *corev1.Affinity) *ScanJobBuilder {
s.affinity = affinity
return s
}

func (s *ScanJobBuilder) WithTolerations(tolerations []corev1.Toleration) *ScanJobBuilder {
s.tolerations = tolerations
return s
Expand Down Expand Up @@ -135,6 +141,10 @@ func (s *ScanJobBuilder) Get() (*batchv1.Job, []*corev1.Secret, error) {
return nil, nil, err
}

if s.affinity != nil {
templateSpec.Affinity = s.affinity
}

templateSpec.Tolerations = append(templateSpec.Tolerations, s.tolerations...)
if s.podSecurityContext != nil {
templateSpec.SecurityContext = s.podSecurityContext
Expand Down
6 changes: 6 additions & 0 deletions pkg/vulnerabilityreport/controller/workload.go
Expand Up @@ -268,6 +268,11 @@ func (r *WorkloadController) SubmitScanJob(ctx context.Context, owner client.Obj
}
}

scanJobAffinity, err := r.GetScanJobAffinity()
if err != nil {
return fmt.Errorf("getting scan job affinity: %w", err)
}

scanJobTolerations, err := r.GetScanJobTolerations()
if err != nil {
return fmt.Errorf("getting scan job tolerations: %w", err)
Expand Down Expand Up @@ -310,6 +315,7 @@ func (r *WorkloadController) SubmitScanJob(ctx context.Context, owner client.Obj
WithTTL(r.Config.ScanJobTTL).
WithScanSecretTTL(r.Config.ScanSecretTTL).
WithObject(owner).
WithAffinity(scanJobAffinity).
WithTolerations(scanJobTolerations).
WithAnnotations(scanJobAnnotations).
WithNodeSelector(scanJobNodeSelector).
Expand Down

0 comments on commit d62c500

Please sign in to comment.