From 2b0e35559c945a112e3461ff1c37051045b3eb8c Mon Sep 17 00:00:00 2001
From: Soule BA
Date: Wed, 19 Oct 2022 02:42:38 +0200
Subject: [PATCH 1/2] Add cosign verification to the chart Template
If implemented users can reconcile charts with cosign verification
enabled.
Signed-off-by: Soule BA
---
api/v2beta1/helmrelease_types.go | 21 +++++
api/v2beta1/zz_generated.deepcopy.go | 25 ++++++
.../helm.toolkit.fluxcd.io_helmreleases.yaml | 28 +++++++
controllers/helmrelease_controller_chart.go | 15 ++++
.../helmrelease_controller_chart_test.go | 48 +++++++++++
docs/api/helmrelease.md | 83 +++++++++++++++++++
go.mod | 6 +-
go.sum | 12 +--
8 files changed, 229 insertions(+), 9 deletions(-)
diff --git a/api/v2beta1/helmrelease_types.go b/api/v2beta1/helmrelease_types.go
index b9cfc31e1..170c98e8a 100644
--- a/api/v2beta1/helmrelease_types.go
+++ b/api/v2beta1/helmrelease_types.go
@@ -286,6 +286,14 @@ type HelmChartTemplateSpec struct {
// +optional
// +deprecated
ValuesFile string `json:"valuesFile,omitempty"`
+
+ // Verify contains the secret name containing the trusted public keys
+ // used to verify the signature and specifies which provider to use to check
+ // whether OCI image is authentic.
+ // This field is only supported for OCI sources.
+ // Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.
+ // +optional
+ Verify *HelmChartTemplateVerification `json:"verify,omitempty"`
}
// GetInterval returns the configured interval for the v1beta2.HelmChart,
@@ -306,6 +314,19 @@ func (in HelmChartTemplate) GetNamespace(defaultNamespace string) string {
return in.Spec.SourceRef.Namespace
}
+// HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.
+type HelmChartTemplateVerification struct {
+ // Provider specifies the technology used to sign the OCI Helm chart.
+ // +kubebuilder:validation:Enum=cosign
+ // +kubebuilder:default:=cosign
+ Provider string `json:"provider"`
+
+ // SecretRef specifies the Kubernetes Secret containing the
+ // trusted public keys.
+ // +optional
+ SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
+}
+
// DeploymentAction defines a consistent interface for Install and Upgrade.
// +kubebuilder:object:generate=false
type DeploymentAction interface {
diff --git a/api/v2beta1/zz_generated.deepcopy.go b/api/v2beta1/zz_generated.deepcopy.go
index 7f8ba2992..6c3f4c541 100644
--- a/api/v2beta1/zz_generated.deepcopy.go
+++ b/api/v2beta1/zz_generated.deepcopy.go
@@ -74,6 +74,11 @@ func (in *HelmChartTemplateSpec) DeepCopyInto(out *HelmChartTemplateSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.Verify != nil {
+ in, out := &in.Verify, &out.Verify
+ *out = new(HelmChartTemplateVerification)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateSpec.
@@ -86,6 +91,26 @@ func (in *HelmChartTemplateSpec) DeepCopy() *HelmChartTemplateSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HelmChartTemplateVerification) DeepCopyInto(out *HelmChartTemplateVerification) {
+ *out = *in
+ if in.SecretRef != nil {
+ in, out := &in.SecretRef, &out.SecretRef
+ *out = new(meta.LocalObjectReference)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateVerification.
+func (in *HelmChartTemplateVerification) DeepCopy() *HelmChartTemplateVerification {
+ if in == nil {
+ return nil
+ }
+ out := new(HelmChartTemplateVerification)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HelmRelease) DeepCopyInto(out *HelmRelease) {
*out = *in
diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
index bd385332e..3e0021fd2 100644
--- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
+++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
@@ -117,6 +117,34 @@ spec:
items:
type: string
type: array
+ verify:
+ description: Verify contains the secret name containing the
+ trusted public keys used to verify the signature and specifies
+ which provider to use to check whether OCI image is authentic.
+ This field is only supported for OCI sources. Chart dependencies,
+ which are not bundled in the umbrella chart artifact, are
+ not verified.
+ properties:
+ provider:
+ default: cosign
+ description: Provider specifies the technology used to
+ sign the OCI Helm chart.
+ enum:
+ - cosign
+ type: string
+ secretRef:
+ description: SecretRef specifies the Kubernetes Secret
+ containing the trusted public keys.
+ properties:
+ name:
+ description: Name of the referent.
+ type: string
+ required:
+ - name
+ type: object
+ required:
+ - provider
+ type: object
version:
default: '*'
description: Version semver expression, ignored for charts
diff --git a/controllers/helmrelease_controller_chart.go b/controllers/helmrelease_controller_chart.go
index 60e369411..912a7b8e4 100644
--- a/controllers/helmrelease_controller_chart.go
+++ b/controllers/helmrelease_controller_chart.go
@@ -211,6 +211,7 @@ func buildHelmChartFromTemplate(hr *v2.HelmRelease) *sourcev1.HelmChart {
ReconcileStrategy: template.Spec.ReconcileStrategy,
ValuesFiles: template.Spec.ValuesFiles,
ValuesFile: template.Spec.ValuesFile,
+ Verify: templateVerificationToSourceVerification(template.Spec.Verify),
},
}
}
@@ -239,7 +240,21 @@ func helmChartRequiresUpdate(hr *v2.HelmRelease, chart *sourcev1.HelmChart) bool
return true
case template.Spec.ValuesFile != chart.Spec.ValuesFile:
return true
+ case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify):
+ return true
default:
return false
}
}
+
+// templateVerificationToSourceVerification converts the HelmChartTemplateVerification to the OCIRepositoryVerification.
+func templateVerificationToSourceVerification(template *v2.HelmChartTemplateVerification) *sourcev1.OCIRepositoryVerification {
+ if template == nil {
+ return nil
+ }
+
+ return &sourcev1.OCIRepositoryVerification{
+ Provider: template.Provider,
+ SecretRef: template.SecretRef,
+ }
+}
diff --git a/controllers/helmrelease_controller_chart_test.go b/controllers/helmrelease_controller_chart_test.go
index 6dcf1c815..43001e5d4 100644
--- a/controllers/helmrelease_controller_chart_test.go
+++ b/controllers/helmrelease_controller_chart_test.go
@@ -18,9 +18,11 @@ package controllers
import (
"context"
+ "fmt"
"testing"
"time"
+ "github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/go-logr/logr"
. "github.com/onsi/gomega"
@@ -371,6 +373,39 @@ func Test_buildHelmChartFromTemplate(t *testing.T) {
},
},
},
+ {
+ name: "take cosign verification into account",
+ modify: func(hr *v2.HelmRelease) {
+ hr.Spec.Chart.Spec.Verify = &v2.HelmChartTemplateVerification{
+ Provider: "cosign",
+ SecretRef: &meta.LocalObjectReference{
+ Name: "cosign-key",
+ },
+ }
+ },
+ want: &sourcev1.HelmChart{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "default-test-release",
+ Namespace: "default",
+ },
+ Spec: sourcev1.HelmChartSpec{
+ Chart: "chart",
+ Version: "1.0.0",
+ SourceRef: sourcev1.LocalHelmChartSourceReference{
+ Name: "test-repository",
+ Kind: "HelmRepository",
+ },
+ Interval: metav1.Duration{Duration: 2 * time.Minute},
+ ValuesFiles: []string{"values.yaml"},
+ Verify: &sourcev1.OCIRepositoryVerification{
+ Provider: "cosign",
+ SecretRef: &meta.LocalObjectReference{
+ Name: "cosign-key",
+ },
+ },
+ },
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -398,6 +433,9 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
Kind: "HelmRepository",
},
Interval: &metav1.Duration{Duration: 2 * time.Minute},
+ Verify: &v2.HelmChartTemplateVerification{
+ Provider: "cosign",
+ },
},
},
},
@@ -469,6 +507,13 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
},
want: true,
},
+ {
+ name: "detects verify change",
+ modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
+ hr.Spec.Chart.Spec.Verify.Provider = "foo-bar"
+ },
+ want: true,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -476,9 +521,12 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
hr := hrWithChartTemplate.DeepCopy()
hc := buildHelmChartFromTemplate(hr)
+ // second copy to avoid modifying the original
+ hr = hrWithChartTemplate.DeepCopy()
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(false))
tt.modify(hr, hc)
+ fmt.Println("verify", hr.Spec.Chart.Spec.Verify.Provider, hc.Spec.Verify.Provider)
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(tt.want))
})
}
diff --git a/docs/api/helmrelease.md b/docs/api/helmrelease.md
index 6b72ba5c6..b53cf1962 100644
--- a/docs/api/helmrelease.md
+++ b/docs/api/helmrelease.md
@@ -566,6 +566,24 @@ for backwards compatibility the file defined here is merged before the
ValuesFiles items. Ignored when omitted.
+
+
+verify
+
+
+HelmChartTemplateVerification
+
+
+ |
+
+(Optional)
+ Verify contains the secret name containing the trusted public keys
+used to verify the signature and specifies which provider to use to check
+whether OCI image is authentic.
+This field is only supported for OCI sources.
+Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.
+ |
+
@@ -688,6 +706,71 @@ for backwards compatibility the file defined here is merged before the
ValuesFiles items. Ignored when omitted.
+
+
+verify
+
+
+HelmChartTemplateVerification
+
+
+ |
+
+(Optional)
+ Verify contains the secret name containing the trusted public keys
+used to verify the signature and specifies which provider to use to check
+whether OCI image is authentic.
+This field is only supported for OCI sources.
+Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.
+ |
+
+
+
+
+
+
+
+(Appears on:
+HelmChartTemplateSpec)
+
+HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.
+