Skip to content

Commit

Permalink
feat: opt-in creating wg-policy PolicyReport
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgb committed Jul 17, 2024
1 parent 2c085ec commit ce41f56
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 4 deletions.
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {
}

pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
config.DefaultMutableFeatureGate.AddFlag(pflag.CommandLine)
pflag.Parse()

logger := zap.New(zap.UseFlagOptions(&opts))
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
k8s.io/client-go v0.30.2
k8s.io/component-base v0.30.2
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/cli-utils v0.37.2
Expand All @@ -29,6 +30,7 @@ require (

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -231,6 +233,8 @@ k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
k8s.io/component-base v0.30.2 h1:pqGBczYoW1sno8q9ObExUqrYSKhtE5rW3y6gX88GZII=
k8s.io/component-base v0.30.2/go.mod h1:yQLkQDrkK8J6NtP+MGJOws+/PPeEXNpwFixsUI7h/OE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
Expand Down
19 changes: 19 additions & 0 deletions internal/config/feature/feature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package feature

import (
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/component-base/featuregate"

"github.com/statnett/image-scanner-operator/internal/config"
)

// PolicyReport will ensure PolicyReport resources are created for completed scan jobs.
const PolicyReport featuregate.Feature = "PolicyReport"

func init() {
runtime.Must(config.DefaultMutableFeatureGate.Add(defaultFeatureGates))
}

var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
PolicyReport: {Default: false, PreRelease: featuregate.Alpha},
}
15 changes: 15 additions & 0 deletions internal/config/feature_gate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package config

import (
"k8s.io/component-base/featuregate"
)

var (
// DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate.
// Only top-level commands/options setup should make use of this.
DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()

// DefaultFeatureGate is a shared global FeatureGate.
// Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate.
DefaultFeatureGate featuregate.FeatureGate = DefaultMutableFeatureGate
)
7 changes: 6 additions & 1 deletion internal/controller/stas/containerimagescan_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,15 @@ func (p *containerImageScanStatusPatch) apply(ctx context.Context, c client.Clie
var err error
// Repeat until resource fits in api-server by increasing minimum severity on failure.
for severity := *p.minSeverity; severity <= stasv1alpha1.MaxSeverity; severity++ {
p.patch.Status.Vulnerabilities, err = filterVulnerabilities(p.vulnerabilities, severity)
var vulnerabilities []stasv1alpha1.Vulnerability
vulnerabilities, err = filterVulnerabilities(p.vulnerabilities, severity)
if err != nil {

Check failure on line 99 in internal/controller/stas/containerimagescan_status.go

View workflow job for this annotation

GitHub Actions / golangci-lint

only one cuddle assignment allowed before if statement (wsl)
return err
}
p.patch.Status.Vulnerabilities = make([]stasv1alpha1ac.VulnerabilityApplyConfiguration, len(vulnerabilities))

Check failure on line 102 in internal/controller/stas/containerimagescan_status.go

View workflow job for this annotation

GitHub Actions / golangci-lint

assignments should only be cuddled with other assignments (wsl)
for i, v := range vulnerabilities {

Check failure on line 103 in internal/controller/stas/containerimagescan_status.go

View workflow job for this annotation

GitHub Actions / golangci-lint

only one cuddle assignment allowed before range statement (wsl)
p.patch.Status.Vulnerabilities[i] = *vulnerabilityPatch(v)
}

err = c.Status().Patch(ctx, p.cis, applyPatch{p.patch}, FieldValidationStrict, client.ForceOwnership, fieldOwner)
if !isResourceTooLargeError(err) {
Expand Down
130 changes: 130 additions & 0 deletions internal/controller/stas/policy_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package stas

import (
"context"
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
policyv1alpha2 "sigs.k8s.io/wg-policy-prototypes/policy-report/pkg/api/wgpolicyk8s.io/v1alpha2"

stasv1alpha1 "github.com/statnett/image-scanner-operator/api/stas/v1alpha1"
policyv1alpha2ac "github.com/statnett/image-scanner-operator/internal/wg-policy/applyconfiguration/wgpolicyk8s.io/v1alpha2"
)

func newPolicyReportPatch(cis *stasv1alpha1.ContainerImageScan) *policyReportPatch {
return &policyReportPatch{
cis: cis,
patch: policyv1alpha2ac.PolicyReport(cis.Name, cis.Namespace).
WithScope(
corev1.ObjectReference{
Kind: cis.Spec.Workload.Kind,
Name: cis.Spec.Workload.Name,
},
),
}
}

type policyReportPatch struct {
cis *stasv1alpha1.ContainerImageScan
patch *policyv1alpha2ac.PolicyReportApplyConfiguration
vulnerabilities []stasv1alpha1.Vulnerability
minSeverity stasv1alpha1.Severity
}

func (p *policyReportPatch) withSummary(summary stasv1alpha1.VulnerabilitySummary) *policyReportPatch {

Check failure on line 37 in internal/controller/stas/policy_report.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `(*policyReportPatch).withSummary` is unused (unused)
p.patch.
WithSummary(
policyv1alpha2ac.PolicyReportSummary().
WithSkip(int(summary.SeverityCount[stasv1alpha1.SeverityUnknown.String()])).
WithWarn(int(summary.SeverityCount[stasv1alpha1.SeverityLow.String()] + summary.SeverityCount[stasv1alpha1.SeverityMedium.String()])).
WithFail(int(summary.SeverityCount[stasv1alpha1.SeverityHigh.String()] + summary.SeverityCount[stasv1alpha1.SeverityCritical.String()])),
)

return p
}

func (p *policyReportPatch) withResults(vulnerabilities []stasv1alpha1.Vulnerability, minSeverity stasv1alpha1.Severity) *policyReportPatch {
p.vulnerabilities = vulnerabilities
p.minSeverity = minSeverity
return p

Check failure on line 52 in internal/controller/stas/policy_report.go

View workflow job for this annotation

GitHub Actions / golangci-lint

return statements should not be cuddled if block has more than two lines (wsl)
}

func (p *policyReportPatch) apply(ctx context.Context, c client.Client, scheme *runtime.Scheme) error {
if err := SetControllerReference(p.cis, p.patch.ObjectMetaApplyConfiguration, scheme); err != nil {
return err
}

var err error
// Repeat until resource fits in api-server by increasing minimum severity on failure.
for severity := p.minSeverity; severity <= stasv1alpha1.MaxSeverity; severity++ {
var vulnerabilities []stasv1alpha1.Vulnerability
vulnerabilities, err = filterVulnerabilities(p.vulnerabilities, severity)
if err != nil {

Check failure on line 65 in internal/controller/stas/policy_report.go

View workflow job for this annotation

GitHub Actions / golangci-lint

only one cuddle assignment allowed before if statement (wsl)
return err
}

p.patch.Results = make([]policyv1alpha2ac.PolicyReportResultApplyConfiguration, len(vulnerabilities))
for i, v := range vulnerabilities {
p.patch.Results[i] = *policyReportResultPatch(v)
}

err = c.Status().Patch(ctx, p.cis, applyPatch{p.patch}, FieldValidationStrict, client.ForceOwnership, fieldOwner)
if !isResourceTooLargeError(err) {
break
}
}

if err != nil {
return fmt.Errorf("when patching status: %w", err)
}

return nil
}

func policyReportResultPatch(v stasv1alpha1.Vulnerability) *policyv1alpha2ac.PolicyReportResultApplyConfiguration {
properties := map[string]string{
"pkgName": v.PkgName,
"installedVersion": v.InstalledVersion,
}
if v.PkgPath != "" {
properties["pkgPath"] = v.PkgPath
}
if v.FixedVersion != "" {

Check failure on line 95 in internal/controller/stas/policy_report.go

View workflow job for this annotation

GitHub Actions / golangci-lint

if statements should only be cuddled with assignments (wsl)
properties["fixedVersion"] = v.FixedVersion
}
if v.PrimaryURL != "" {

Check failure on line 98 in internal/controller/stas/policy_report.go

View workflow job for this annotation

GitHub Actions / golangci-lint

if statements should only be cuddled with assignments (wsl)
properties["primaryURL"] = v.PrimaryURL
}

return policyv1alpha2ac.PolicyReportResult().
WithCategory("vulnerability scan").
WithSource("image-scanner").
WithPolicy(v.VulnerabilityID).
WithResult(mapPolicyResult(v.Severity)).
WithSeverity(mapPolicyResultSeverity(v.Severity)).
WithDescription(v.Title).
WithProperties(properties)
}

func mapPolicyResult(severity string) policyv1alpha2.PolicyResult {
switch severity {
case "UNKNOWN":
return ""
default:
return policyv1alpha2.PolicyResult(strings.ToLower(severity))
}
}

func mapPolicyResultSeverity(severity string) policyv1alpha2.PolicyResultSeverity {
switch severity {
case "UNKNOWN":
return "skip"
case "LOW", "MEDIUM":
return "warn"
default:
return "fail"
}
}
16 changes: 13 additions & 3 deletions internal/controller/stas/scan_job_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
stasv1alpha1 "github.com/statnett/image-scanner-operator/api/stas/v1alpha1"
stasv1alpha1ac "github.com/statnett/image-scanner-operator/internal/client/applyconfiguration/stas/v1alpha1"
"github.com/statnett/image-scanner-operator/internal/config"
"github.com/statnett/image-scanner-operator/internal/config/feature"
"github.com/statnett/image-scanner-operator/internal/controller"
staserrors "github.com/statnett/image-scanner-operator/internal/errors"
"github.com/statnett/image-scanner-operator/internal/pod"
Expand Down Expand Up @@ -182,6 +183,15 @@ func (r *ScanJobReconciler) reconcileCompleteJob(ctx context.Context, job *batch
}
}

if config.DefaultFeatureGate.Enabled(feature.PolicyReport) {
err = newPolicyReportPatch(cis).
withResults(vulnerabilities, minSeverity).
apply(ctx, r.Client, r.Scheme)
if err != nil {
return err
}
}

return newContainerImageStatusPatch(cis).
withCompletedScanJob(job, vulnerabilities, minSeverity).
apply(ctx, r.Client)
Expand Down Expand Up @@ -339,8 +349,8 @@ func (r *ScanJobReconciler) getScanJobLogs(ctx context.Context, job *batchv1.Job
return r.GetLogs(ctx, client.ObjectKeyFromObject(&jobPod), trivy.ScanJobContainerName)
}

func filterVulnerabilities(orig []stasv1alpha1.Vulnerability, minSeverity stasv1alpha1.Severity) ([]stasv1alpha1ac.VulnerabilityApplyConfiguration, error) {
var filtered []stasv1alpha1ac.VulnerabilityApplyConfiguration
func filterVulnerabilities(orig []stasv1alpha1.Vulnerability, minSeverity stasv1alpha1.Severity) ([]stasv1alpha1.Vulnerability, error) {
var filtered []stasv1alpha1.Vulnerability

for _, v := range orig {
severity, err := stasv1alpha1.NewSeverity(v.Severity)
Expand All @@ -349,7 +359,7 @@ func filterVulnerabilities(orig []stasv1alpha1.Vulnerability, minSeverity stasv1
}

if severity >= minSeverity {
filtered = append(filtered, *vulnerabilityPatch(v))
filtered = append(filtered, v)
}
}

Expand Down
29 changes: 29 additions & 0 deletions internal/controller/stas/ssa_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,35 @@ func NewConditionsPatch(existingConditions []metav1.Condition, conditions ...*me
return conditions
}

// SetControllerReference sets owner as a Controller OwnerReference on controlled.
// This is used for garbage collection of the controlled object and for
// reconciling the owner object on changes to controlled (with a Watch + EnqueueRequestForOwner).
func SetControllerReference(owner metav1.Object, controlled *metav1ac.ObjectMetaApplyConfiguration, scheme *runtime.Scheme) error {
// Validate the owner.
ro, ok := owner.(runtime.Object)
if !ok {
return fmt.Errorf("%T is not a runtime.Object, cannot call SetControllerReference", owner)
}
if err := validateOwner(owner, controlled); err != nil {

Check failure on line 105 in internal/controller/stas/ssa_client.go

View workflow job for this annotation

GitHub Actions / golangci-lint

if statements should only be cuddled with assignments (wsl)
return err
}

gvk, err := apiutil.GVKForObject(ro, scheme)
if err != nil {
return err
}
controlled.WithOwnerReferences(

Check failure on line 113 in internal/controller/stas/ssa_client.go

View workflow job for this annotation

GitHub Actions / golangci-lint

expressions should not be cuddled with blocks (wsl)
metav1ac.OwnerReference().
WithAPIVersion(gvk.GroupVersion().String()).
WithKind(gvk.Kind).
WithName(owner.GetName()).
WithUID(owner.GetUID()).
WithBlockOwnerDeletion(true).
WithController(true),
)
return nil
}

// SetOwnerReference is a helper method to make sure the given object contains an object reference to the object provided.
// This allows you to declare that owner has a dependency on the object without specifying it as a controller.
// If a reference to the same object already exists, it'll be overwritten with the newly provided version.
Expand Down

0 comments on commit ce41f56

Please sign in to comment.