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
JpX&fp003)zj$r@*
diff --git a/internal/helm/testdata/charts/helmchart-0.1.0.tgz.prov b/internal/helm/testdata/charts/helmchart-0.1.0.tgz.prov
new file mode 100644
index 000000000..573d72a91
--- /dev/null
+++ b/internal/helm/testdata/charts/helmchart-0.1.0.tgz.prov
@@ -0,0 +1,26 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+apiVersion: v2
+appVersion: 1.16.0
+description: A Helm chart for Kubernetes
+name: helmchart
+type: application
+version: 0.1.0
+
+...
+files:
+ helmchart-0.1.0.tgz: sha256:c6c5a41ff83f415e18d2ed8ab3a386021e3f1742ea3d1bc0ba759a09aaeb8f2a
+-----BEGIN PGP SIGNATURE-----
+
+wsDcBAEBCgAQBQJiIwjxCRCDj6SUu5W1LwAAndgMAEDZsARwLEczJYw3lKYeftcy
+lebfr81jxNS1a4J2DQvOEcltdA1MBHBoir3GoG53xjMWMYMKLUj0FQLQFoLwHTvf
+zl5KkfMQnH85KL5TAbzm+Oiz/WiKYQ9cza5T+50WoXFVjdfoF6efZ6tOxV+FtS/o
+toga+N8z4FtkhbuY0qQx4nxM2wRd/XZHPFO0LRx+Z8E5lghedLOD7ocV7kN/FD9p
+0/MMZ5kpeLevfnp4GBYjZKxojH8eOFni7WPovHUts/QHvEnYxucNwej8OTIy699w
+APJbwEV3BVwzjqgsfywQxH80JEpNGzCRUt5yfnXpF4IUxzPVM1z2tp+/leoHxkxw
+yfhL8FVfUbWnWvqIMY8QK6zhkqy22jb4lFE7jPEUwVu+HQc6KTzkYhZQQ5fOHmoa
+pYsCbAF/AMZ1U8yT6OljVF904yIiLohR7F6s/maEu6mCdy82sNjpCdasuThqe7k0
+Hv4m4NcrULqhvKyyAqp/XgWMPeNuFkq3wqBk1DxLTw==
+=oQfE
+-----END PGP SIGNATURE-----
\ No newline at end of file
diff --git a/internal/helm/testdata/charts/pub.gpg b/internal/helm/testdata/charts/pub.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..5b711ded67cd4b2b26838340856433e954392889
GIT binary patch
literal 1728
zcmV;x20!_k0gVJ=BM5{63;@n2aww|z0{Kp_QRBdB8SNamkuLtrpS{I8#Ahr0WtOJ0
z-qTmHqMEcfC|Kgt(^(hs&Gn{Q6@fXI;a>>d-I1!$N02Fx!VP+qmd=t0|LQ^CEZq%R
z{%Og@3`8inY*Vdvzc-7~X3g`9QBYBIYi<&Q!9prlCuhGJiJCIb&T_vXSoV+EjSoXl
zwwj9bfB+ekEN%LQTRhFYtsAAqZHLzLe_Swo#EeXAf*;ywu0-!J=
z=AxQcv_?dCR^YVGztF|Kw%m;;7`!x+*de6HI$%D8bjskGN9O<$0RREC2vlWrbX9X@
za)|-d1QP)W06rEW1h>+p$JaO5wpg_lT!W9Kl)II+F9iZ(BM5{78v_Li1Hxwj1q%rX
z2Lc8a3JC}c0t6NU0|5da0Rk6*0162ZgO8+?yOp&sE^!P0L%{a=c)M$~Dv%4LOV5V%
z=?8w{aVV{s0zVkuzfUcfH8WC@dZD~~RDcwr6j3@bH=W)yK&+~*SGy$Fj{#}tjP
z>(DL+G_ftYTPnC(LP5bMnJ_+hCPE*!h@D!aK2xV~&lhDbc{mYV_v9Y2!rr2870Ekv
zpodQLQrZU7Y-nF^!j6Dcx3-)Yg8AS3;?)?q`<*a=dS7Sgc+wj<5>ron)=fRcPzwf2aGLCAs6L@m`
z%=#b+xdDv?Vj~EI0So}%8vc@v(?^
znMiO+Gop60nY<_KZwIlsT763058tb76_A%!dwmqCy)?<=2nu#ilJ%M?mm*@GyC&u_
zee#%_+VlC@eWZJ?pbn~E5R;$RtaB9^p4dnV(ozv
zNuRpCW+%3IFKZPMksVBE&*S#9dpiXPx{*mH9%I$m=4u4|6u7-d@0zibXk?fY9|
z5^w_|1r68Z-%S{p4cKhjHWL`Dq8h1Tl;?b{w_{u8?SIbz5di=Ji2=L>7y$?XCKe$C
zx6-7?*EiUggO8+?yOp&s1p;Ct2!sL~3m>-HhR5ObgsRds^kZ0)JR0$(Kt<&M^4#2rNtQJ)=zZhYhuK*}X_bBF&v!8gzOI)03xuffc*Y}yx9ibDb+=q>dRpj%6?Q^APhZXW6pAdf(XRF>aWpSCf(vWgRU3
zaW#z}o|_c)yX@F`BR1)0RiN#z2ePwXx?PK*S4lARlp)*NcUSf!DRous_a>(l?*OvEoMTsSO27OLXO3
z`29)o)z|(HT|lJ62slzdq!AxX@zwTIE!N}gU5JnbS~X}yE7>^M_PN|sYUpQV4i1~)
z7*JUO9_$jDLVy@bJP=d0;gK+Dp7CDcodqb#Nh_;R+27|Bnd$*{m1wZoewlSZvhKF4
WPWX$$0q&Hnc)}yAh!E^KfBh}6CO8ZL
literal 0
HcmV?d00001
diff --git a/internal/helm/testdata/charts/sec.gpg b/internal/helm/testdata/charts/sec.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..9df8a2779b3b68c7592b190ad97dde8bedf7acad
GIT binary patch
literal 3670
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
literal 0
HcmV?d00001
From d435fe7b22de40e9cd773e317d70b9d4342bd058 Mon Sep 17 00:00:00 2001
From: Sanskar Jaiswal
Date: Tue, 8 Mar 2022 21:57:24 +0530
Subject: [PATCH 03/10] update helmchart reconciliation to verify charts using
prov file
Signed-off-by: Sanskar Jaiswal
---
api/v1beta2/helmchart_types.go | 14 ++++
.../source.toolkit.fluxcd.io_helmcharts.yaml | 19 +++++
controllers/helmchart_controller.go | 79 ++++++++++++++++++-
controllers/storage.go | 3 +-
internal/helm/chart/builder_local.go | 6 +-
internal/helm/chart/builder_remote.go | 2 +-
internal/helm/chart/verify.go | 10 +--
7 files changed, 122 insertions(+), 11 deletions(-)
diff --git a/api/v1beta2/helmchart_types.go b/api/v1beta2/helmchart_types.go
index 2ce5a942f..660356535 100644
--- a/api/v1beta2/helmchart_types.go
+++ b/api/v1beta2/helmchart_types.go
@@ -84,6 +84,20 @@ type HelmChartSpec struct {
// NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092
// +optional
AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"`
+
+ // Keyring information for verifying the packaged chart's signature using a provenance file.
+ // +optional
+ VerificationKeyring *VerificationKeyring `json:"verificationKeyring,omitempty"`
+}
+
+type VerificationKeyring struct {
+ // +required
+ SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
+
+ // The key that corresponds to the keyring value.
+ // +kubebuilder:default:=pubring.gpg
+ // +optional
+ Key string `json:"key,omitempty"`
}
const (
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml b/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml
index a45d0370b..1671ac596 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml
@@ -404,6 +404,25 @@ spec:
items:
type: string
type: array
+ verificationKeyring:
+ description: Keyring information for verifying the packaged chart's
+ signature using a provenance file.
+ properties:
+ key:
+ default: pubring.gpg
+ description: The key that corresponds to the keyring value.
+ type: string
+ secretRef:
+ description: LocalObjectReference contains enough information
+ to locate the referenced Kubernetes resource object.
+ properties:
+ name:
+ description: Name of the referent.
+ type: string
+ required:
+ - name
+ type: object
+ type: object
version:
default: '*'
description: Version is the chart version semver expression, ignored
diff --git a/controllers/helmchart_controller.go b/controllers/helmchart_controller.go
index 3fa0c0271..3c700830b 100644
--- a/controllers/helmchart_controller.go
+++ b/controllers/helmchart_controller.go
@@ -95,6 +95,8 @@ var helmChartReadyCondition = summarize.Conditions{
},
}
+const KeyringFileName = "pubring.gpg"
+
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/finalizers,verbs=get;create;update;patch;delete
@@ -467,13 +469,20 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
opts.VersionMetadata = strconv.FormatInt(obj.Generation, 10)
}
+ var keyring []byte
+ keyring, err = r.getProvenanceKeyring(ctx, obj)
+ if err != nil {
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, err.Error())
+ return sreconcile.ResultEmpty, err
+ }
+
// Build the chart
ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version}
- build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts)
+ build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts, keyring)
+
if err != nil {
return sreconcile.ResultEmpty, err
}
-
*b = *build
return sreconcile.ResultSuccess, nil
}
@@ -590,13 +599,19 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
}
opts.VersionMetadata += strconv.FormatInt(obj.Generation, 10)
}
+ var keyring []byte
+ keyring, err = r.getProvenanceKeyring(ctx, obj)
+ if err != nil {
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, err.Error())
+ return sreconcile.ResultEmpty, err
+ }
// Build chart
cb := chart.NewLocalBuilder(dm)
build, err := cb.Build(ctx, chart.LocalReference{
WorkDir: sourceDir,
Path: chartPath,
- }, util.TempPathForObj("", ".tgz", obj), opts)
+ }, util.TempPathForObj("", ".tgz", obj), opts, keyring)
if err != nil {
return sreconcile.ResultEmpty, err
}
@@ -670,6 +685,18 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
return sreconcile.ResultEmpty, e
}
+ // the provenance file artifact is not recorded, but it shadows the HelmChart artifact
+ // under the assumption that the file is always available at "chart.tgz.prov"
+ if b.ProvFilePath != "" {
+ provArtifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), b.Version, fmt.Sprintf("%s-%s.tgz.prov", b.Name, b.Version))
+ if err = r.Storage.CopyFromPath(&provArtifact, b.ProvFilePath); err != nil {
+ return sreconcile.ResultEmpty, &serror.Event{
+ Err: fmt.Errorf("unable to copy Helm chart provenance file to storage: %w", err),
+ Reason: sourcev1.StorageOperationFailedReason,
+ }
+ }
+ }
+
// Record it on the object
obj.Status.Artifact = artifact.DeepCopy()
obj.Status.ObservedChartName = b.Name
@@ -861,6 +888,22 @@ func (r *HelmChartReconciler) getHelmRepositorySecret(ctx context.Context, repos
return &secret, nil
}
+func (r *HelmChartReconciler) getVerificationKeyringSecret(ctx context.Context, chart *sourcev1.HelmChart) (*corev1.Secret, error) {
+ if chart.Spec.VerificationKeyring == nil {
+ return nil, nil
+ }
+ name := types.NamespacedName{
+ Namespace: chart.GetNamespace(),
+ Name: chart.Spec.VerificationKeyring.SecretRef.Name,
+ }
+ var secret corev1.Secret
+ err := r.Client.Get(ctx, name, &secret)
+ if err != nil {
+ return nil, err
+ }
+ return &secret, nil
+}
+
func (r *HelmChartReconciler) indexHelmRepositoryByURL(o client.Object) []string {
repo, ok := o.(*sourcev1.HelmRepository)
if !ok {
@@ -1021,3 +1064,33 @@ func reasonForBuild(build *chart.Build) string {
}
return sourcev1.ChartPullSucceededReason
}
+
+func (r *HelmChartReconciler) getProvenanceKeyring(ctx context.Context, chart *sourcev1.HelmChart) ([]byte, error) {
+ if chart.Spec.VerificationKeyring == nil {
+ return nil, nil
+ }
+ name := types.NamespacedName{
+ Namespace: chart.GetNamespace(),
+ Name: chart.Spec.VerificationKeyring.SecretRef.Name,
+ }
+ var secret corev1.Secret
+ err := r.Client.Get(ctx, name, &secret)
+ if err != nil {
+ e := &serror.Event{
+ Err: fmt.Errorf("failed to get secret '%s': %w", chart.Spec.VerificationKeyring.SecretRef.Name, err),
+ Reason: sourcev1.AuthenticationFailedReason,
+ }
+ return nil, e
+ }
+ key := chart.Spec.VerificationKeyring.Key
+ if val, ok := secret.Data[key]; !ok {
+ err = fmt.Errorf("secret doesn't contain the advertised verification keyring name %s", key)
+ e := &serror.Event{
+ Err: fmt.Errorf("invalid secret '%s': %w", secret.GetName(), err),
+ Reason: sourcev1.AuthenticationFailedReason,
+ }
+ return nil, e
+ } else {
+ return val, nil
+ }
+}
diff --git a/controllers/storage.go b/controllers/storage.go
index 55f9a077c..f8016f5fe 100644
--- a/controllers/storage.go
+++ b/controllers/storage.go
@@ -120,6 +120,7 @@ func (s *Storage) RemoveAll(artifact sourcev1.Artifact) (string, error) {
func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, error) {
deletedFiles := []string{}
localPath := s.LocalPath(artifact)
+ localProvPath := localPath + ".prov"
dir := filepath.Dir(localPath)
var errors []string
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
@@ -128,7 +129,7 @@ func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, err
return nil
}
- if path != localPath && !info.IsDir() && info.Mode()&os.ModeSymlink != os.ModeSymlink {
+ if path != localPath && path != localProvPath && !info.IsDir() && info.Mode()&os.ModeSymlink != os.ModeSymlink {
if err := os.Remove(path); err != nil {
errors = append(errors, info.Name())
} else {
diff --git a/internal/helm/chart/builder_local.go b/internal/helm/chart/builder_local.go
index 760280b62..cbbe840b8 100644
--- a/internal/helm/chart/builder_local.go
+++ b/internal/helm/chart/builder_local.go
@@ -148,15 +148,19 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
// If the chart at the path is already packaged and no custom values files
// options are set, we can copy the chart without making modifications
- provFilePath = provenanceFilePath(localRef.Path)
if !requiresPackaging {
+ provFilePath = provenanceFilePath(p)
if err = copyFileToPath(localRef.Path, p); err != nil {
return result, &BuildError{Reason: ErrChartPull, Err: err}
}
+ if err = copyFileToPath(provenanceFilePath(localRef.Path), provFilePath); err != nil {
+ return result, &BuildError{Reason: ErrChartPull, Err: err}
+ }
if err = verifyProvFile(localRef.Path, provFilePath); err != nil {
return result, err
}
result.Path = p
+ result.ProvFilePath = provFilePath
return result, nil
}
diff --git a/internal/helm/chart/builder_remote.go b/internal/helm/chart/builder_remote.go
index a159c5f1a..54e1bed8c 100644
--- a/internal/helm/chart/builder_remote.go
+++ b/internal/helm/chart/builder_remote.go
@@ -166,7 +166,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
// This is needed, since the verification will work only if the .tgz file is untampered.
// But we write the packaged chart to disk under a different name, so the provenance file
// will not be valid for this _new_ packaged chart.
- chart, err := util.WriteBytesToFile(chartBuf, fmt.Sprintf("%s-%s.tgz", result.Name, result.Version), false)
+ chart, err := util.WriteBytesToFile(chartBuf, fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version), false)
defer os.Remove(chart.Name())
if err != nil {
return nil, err
diff --git a/internal/helm/chart/verify.go b/internal/helm/chart/verify.go
index 0600acd7e..26e91c31e 100644
--- a/internal/helm/chart/verify.go
+++ b/internal/helm/chart/verify.go
@@ -11,6 +11,9 @@ import (
"helm.sh/helm/v3/pkg/provenance"
)
+// Ref: https://github.com/helm/helm/blob/v3.8.0/pkg/downloader/chart_downloader.go#L328
+// modified to accept a custom provenance file path and an actual keyring instead of a
+// path to the file containing the keyring.
func VerifyProvenanceFile(keyring io.Reader, chartPath, provFilePath string) error {
switch fi, err := os.Stat(chartPath); {
case err != nil:
@@ -43,15 +46,12 @@ func VerifyProvenanceFile(keyring io.Reader, chartPath, provFilePath string) err
}
// isTar tests whether the given file is a tar file.
-//
-// Currently, this simply checks extension, since a subsequent function will
-// untar the file and validate its binary format.
func isTar(filename string) bool {
return strings.EqualFold(filepath.Ext(filename), ".tgz")
}
-// Returns the path of a provenance file related to a packaged chart
-// Adds ".prov" at the end, as per the Helm convention.
+// Returns the path of a provenance file related to a packaged chart by
+// adding ".prov" at the end, as per the Helm convention.
func provenanceFilePath(path string) string {
return path + ".prov"
}
From 4988803679c22534e928defd35572297d06fc5d9 Mon Sep 17 00:00:00 2001
From: Sanskar Jaiswal
Date: Tue, 8 Mar 2022 21:59:05 +0530
Subject: [PATCH 04/10] update helmchart reconciler tests for prov file
verification
Signed-off-by: Sanskar Jaiswal
---
controllers/helmchart_controller_test.go | 392 ++++++++++++++++--
.../testdata/charts/helmchart-0.1.0.tgz | Bin 3277 -> 3280 bytes
.../testdata/charts/helmchart-0.1.0.tgz.prov | 26 ++
controllers/testdata/charts/pub.gpg | Bin 0 -> 1728 bytes
controllers/testdata/charts/sec.gpg | Bin 0 -> 3670 bytes
5 files changed, 390 insertions(+), 28 deletions(-)
create mode 100644 controllers/testdata/charts/helmchart-0.1.0.tgz.prov
create mode 100644 controllers/testdata/charts/pub.gpg
create mode 100644 controllers/testdata/charts/sec.gpg
diff --git a/controllers/helmchart_controller_test.go b/controllers/helmchart_controller_test.go
index 43d568b85..5390f57a4 100644
--- a/controllers/helmchart_controller_test.go
+++ b/controllers/helmchart_controller_test.go
@@ -52,6 +52,8 @@ import (
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
)
+const publicKeyFileName = "pub.pgp"
+
func TestHelmChartReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)
@@ -65,7 +67,8 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(server.Root())
- g.Expect(server.PackageChartWithVersion(chartPath, chartVersion)).To(Succeed())
+ publicKeyPath := fmt.Sprintf("%s/%s", server.Root(), publicKeyFileName)
+ g.Expect(server.PackageSignedChartWithVersion(chartPath, chartVersion, publicKeyPath)).To(Succeed())
g.Expect(server.GenerateIndex()).To(Succeed())
server.Start()
@@ -86,6 +89,21 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
}
g.Expect(testEnv.CreateAndWait(ctx, repository)).To(Succeed())
+ keyring, err := os.ReadFile(publicKeyPath)
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(keyring).ToNot(BeEmpty())
+
+ keyringSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret",
+ Namespace: ns.Name,
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring,
+ },
+ }
+ g.Expect(testEnv.CreateAndWait(ctx, keyringSecret)).To(Succeed())
+
obj := &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "helmrepository-reconcile-",
@@ -98,6 +116,12 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
Kind: sourcev1.HelmRepositoryKind,
Name: repository.Name,
},
+ VerificationKeyring: &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: keyringSecret.Name,
+ },
+ Key: publicKeyFileName,
+ },
},
}
g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
@@ -183,12 +207,21 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) {
Path: fmt.Sprintf("/reconcile-storage/%s.txt", v),
Revision: v,
}
+ provArtifact := &sourcev1.Artifact{
+ Path: fmt.Sprintf("/reconcile-storage/%s.txt.prov", v),
+ Revision: v,
+ }
+
if err := testStorage.MkdirAll(*obj.Status.Artifact); err != nil {
return err
}
+
if err := testStorage.AtomicWriteFile(obj.Status.Artifact, strings.NewReader(v), 0644); err != nil {
return err
}
+ if err := testStorage.AtomicWriteFile(provArtifact, strings.NewReader(v), 0644); err != nil {
+ return err
+ }
}
testStorage.SetArtifactURL(obj.Status.Artifact)
return nil
@@ -204,6 +237,9 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) {
"/reconcile-storage/c.txt",
"!/reconcile-storage/b.txt",
"!/reconcile-storage/a.txt",
+ "/reconcile-storage/c.txt.prov",
+ "!/reconcile-storage/b.txt.prov",
+ "!/reconcile-storage/a.txt.prov",
},
want: sreconcile.ResultSuccess,
},
@@ -311,9 +347,13 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
}
g.Expect(storage.Archive(gitArtifact, "testdata/charts", nil)).To(Succeed())
+ keyring, err := os.ReadFile("testdata/charts/pub.gpg")
+ g.Expect(err).ToNot(HaveOccurred())
+
tests := []struct {
name string
source sourcev1.Source
+ secret *corev1.Secret
beforeFunc func(obj *sourcev1.HelmChart)
want sreconcile.Result
wantErr error
@@ -331,12 +371,27 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
Artifact: gitArtifact,
},
},
+ secret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring,
+ },
+ },
beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
obj.Spec.SourceRef = sourcev1.LocalHelmChartSourceReference{
Name: "gitrepository",
Kind: sourcev1.GitRepositoryKind,
}
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret",
+ },
+ Key: publicKeyFileName,
+ }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, build chart.Build, obj sourcev1.HelmChart) {
@@ -344,6 +399,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
g.Expect(build.Name).To(Equal("helmchart"))
g.Expect(build.Version).To(Equal("0.1.0"))
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal(gitArtifact.Revision))
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
@@ -352,6 +408,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
+ g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
@@ -458,6 +515,9 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
if tt.source != nil {
clientBuilder.WithRuntimeObjects(tt.source)
}
+ if tt.secret != nil {
+ clientBuilder.WithRuntimeObjects(tt.secret)
+ }
r := &HelmChartReconciler{
Client: clientBuilder.Build(),
@@ -508,33 +568,58 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
)
serverFactory, err := helmtestserver.NewTempHelmServer()
+ publicKeyPath := fmt.Sprintf("%s/%s", serverFactory.Root(), publicKeyFileName)
+
g.Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(serverFactory.Root())
for _, ver := range []string{chartVersion, higherChartVersion} {
- g.Expect(serverFactory.PackageChartWithVersion(chartPath, ver)).To(Succeed())
+ g.Expect(serverFactory.PackageSignedChartWithVersion(chartPath, ver, publicKeyPath+ver)).To(Succeed())
}
g.Expect(serverFactory.GenerateIndex()).To(Succeed())
+ keyring1, err := os.ReadFile(publicKeyPath + chartVersion)
+ g.Expect(err).ToNot(HaveOccurred())
+ defer os.Remove(publicKeyPath + chartVersion)
+
+ keyring2, err := os.ReadFile(publicKeyPath + higherChartVersion)
+ g.Expect(err).ToNot(HaveOccurred())
+ defer os.Remove(publicKeyPath + higherChartVersion)
+
type options struct {
username string
password string
}
tests := []struct {
- name string
- server options
- secret *corev1.Secret
- beforeFunc func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository)
- want sreconcile.Result
- wantErr error
- assertFunc func(g *WithT, obj *sourcev1.HelmChart, build chart.Build)
- cleanFunc func(g *WithT, build *chart.Build)
+ name string
+ server options
+ secret *corev1.Secret
+ keyringSecret *corev1.Secret
+ beforeFunc func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository)
+ want sreconcile.Result
+ wantErr error
+ assertFunc func(g *WithT, obj *sourcev1.HelmChart, build chart.Build)
+ cleanFunc func(g *WithT, build *chart.Build)
}{
{
name: "Reconciles chart build",
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Spec.Chart = "helmchart"
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret-0.3.0",
+ },
+ Key: publicKeyFileName,
+ }
+ },
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret-0.3.0",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring2,
+ },
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
@@ -542,9 +627,12 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(higherChartVersion))
g.Expect(build.Path).ToNot(BeEmpty())
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).ToNot(BeEmpty())
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
+ g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
@@ -553,6 +641,14 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
username: "foo",
password: "bar",
},
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret-0.2.0",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring1,
+ },
+ },
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "auth",
@@ -566,6 +662,12 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
obj.Spec.Chart = chartName
obj.Spec.Version = chartVersion
repository.Spec.SecretRef = &meta.LocalObjectReference{Name: "auth"}
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret-0.2.0",
+ },
+ Key: publicKeyFileName,
+ }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
@@ -573,17 +675,34 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(chartVersion))
g.Expect(build.Path).ToNot(BeEmpty())
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).ToNot(BeEmpty())
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
+ g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
name: "Uses artifact as build cache",
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret-0.2.0",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring1,
+ },
+ },
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Spec.Chart = chartName
obj.Spec.Version = chartVersion
obj.Status.Artifact = &sourcev1.Artifact{Path: chartName + "-" + chartVersion + ".tgz"}
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret-0.2.0",
+ },
+ Key: publicKeyFileName,
+ }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
@@ -591,14 +710,30 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(chartVersion))
g.Expect(build.Path).To(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path)))
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).To(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path+".prov")))
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
},
},
{
name: "Sets Generation as VersionMetadata with values files",
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret-0.3.0",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring2,
+ },
+ },
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Spec.Chart = chartName
obj.Generation = 3
obj.Spec.ValuesFiles = []string{"values.yaml", "override.yaml"}
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret-0.3.0",
+ },
+ Key: publicKeyFileName,
+ }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
@@ -606,13 +741,24 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(higherChartVersion + "+3"))
g.Expect(build.Path).ToNot(BeEmpty())
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).ToNot(BeEmpty())
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
+ g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
name: "Forces build on generation change",
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret-0.2.0",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring1,
+ },
+ },
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Generation = 3
obj.Spec.Chart = chartName
@@ -620,6 +766,12 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
obj.Status.ObservedGeneration = 2
obj.Status.Artifact = &sourcev1.Artifact{Path: chartName + "-" + chartVersion + ".tgz"}
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret-0.2.0",
+ },
+ Key: publicKeyFileName,
+ }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
@@ -627,9 +779,12 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(chartVersion))
g.Expect(build.Path).ToNot(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path)))
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).ToNot(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path+".prov")))
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
+ g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
@@ -713,6 +868,9 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
if tt.secret != nil {
clientBuilder.WithObjects(tt.secret.DeepCopy())
}
+ if tt.keyringSecret != nil {
+ clientBuilder.WithObjects(tt.keyringSecret.DeepCopy())
+ }
storage, err := newTestStorage(server)
g.Expect(err).ToNot(HaveOccurred())
@@ -754,6 +912,9 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
defer tt.cleanFunc(g, &b)
}
got, err := r.buildFromHelmRepository(context.TODO(), obj, repository, &b)
+ if err != nil {
+ t.Logf("error: %s", err)
+ }
g.Expect(err != nil).To(Equal(tt.wantErr != nil))
if tt.wantErr != nil {
@@ -791,18 +952,28 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
g.Expect(storage.CopyFromPath(yamlArtifact, "testdata/charts/helmchart/values.yaml")).To(Succeed())
cachedArtifact := &sourcev1.Artifact{
Revision: "0.1.0",
- Path: "cached.tgz",
+ Path: "helmchart-0.1.0.tgz",
}
g.Expect(storage.CopyFromPath(cachedArtifact, "testdata/charts/helmchart-0.1.0.tgz")).To(Succeed())
+ provArtifact := &sourcev1.Artifact{
+ Revision: "1234smth",
+ Path: "helmchart-0.1.0.tgz.prov",
+ }
+ g.Expect(storage.CopyFromPath(provArtifact, "testdata/charts/helmchart-0.1.0.tgz.prov")).To(Succeed())
+
+ keyring, err := os.ReadFile("testdata/charts/pub.gpg")
+ g.Expect(err).ToNot(HaveOccurred())
+
tests := []struct {
- name string
- source sourcev1.Artifact
- beforeFunc func(obj *sourcev1.HelmChart)
- want sreconcile.Result
- wantErr error
- assertFunc func(g *WithT, build chart.Build)
- cleanFunc func(g *WithT, build *chart.Build)
+ name string
+ source sourcev1.Artifact
+ keyringSecret *corev1.Secret
+ beforeFunc func(obj *sourcev1.HelmChart)
+ want sreconcile.Result
+ wantErr error
+ assertFunc func(g *WithT, build chart.Build)
+ cleanFunc func(g *WithT, build *chart.Build)
}{
{
name: "Resolves chart dependencies and builds",
@@ -866,9 +1037,24 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
{
name: "Chart from storage cache",
source: *chartsArtifact.DeepCopy(),
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring,
+ },
+ },
beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
obj.Status.Artifact = cachedArtifact.DeepCopy()
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret",
+ },
+ Key: publicKeyFileName,
+ }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, build chart.Build) {
@@ -876,11 +1062,21 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
g.Expect(build.Version).To(Equal("0.1.0"))
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.ProvFilePath).To(Equal(storage.LocalPath(*provArtifact.DeepCopy())))
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
},
},
{
name: "Generation change forces rebuild",
source: *chartsArtifact.DeepCopy(),
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring,
+ },
+ },
beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Generation = 2
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
@@ -921,8 +1117,13 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
+ clientBuilder := fake.NewClientBuilder()
+ if tt.keyringSecret != nil {
+ clientBuilder.WithObjects(tt.keyringSecret.DeepCopy())
+ }
+
r := &HelmChartReconciler{
- Client: fake.NewClientBuilder().Build(),
+ Client: clientBuilder.Build(),
EventRecorder: record.NewFakeRecorder(32),
Storage: storage,
Getters: testGetters,
@@ -982,13 +1183,34 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
{
name: "Copying artifact to storage from build makes Ready=True",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
beforeFunc: func(obj *sourcev1.HelmChart) {
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
},
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
t.Expect(obj.GetArtifact()).ToNot(BeNil())
- t.Expect(obj.GetArtifact().Checksum).To(Equal("bbdf96023c912c393b49d5238e227576ed0d20d1bb145d7476d817b80e20c11a"))
+ t.Expect(obj.GetArtifact().Checksum).To(Equal("5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3"))
+ t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
+ t.Expect(obj.Status.URL).ToNot(BeEmpty())
+ t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
+ },
+ want: sreconcile.ResultSuccess,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReadyCondition, sourcev1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
+ },
+ },
+ {
+ name: "A build with a non-nil ProvFilePath persists prov file to storage",
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", "testdata/charts/helmchart-0.1.0.tgz"),
+ beforeFunc: func(obj *sourcev1.HelmChart) {
+ conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
+ },
+ afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
+ provArtifact := testStorage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "0.1.0", fmt.Sprintf("%s-%s.tgz.prov", "helmchart", "0.1.0"))
+ t.Expect(provArtifact.Path).ToNot(BeEmpty())
+ t.Expect(obj.GetArtifact()).ToNot(BeNil())
+ fmt.Printf("checksum: %s", obj.GetArtifact().Checksum)
+ t.Expect(obj.GetArtifact().Checksum).To(Equal("5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3"))
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
t.Expect(obj.Status.URL).ToNot(BeEmpty())
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
@@ -1043,13 +1265,13 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
{
name: "Removes ArtifactOutdatedCondition after creating new artifact",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
beforeFunc: func(obj *sourcev1.HelmChart) {
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
},
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
t.Expect(obj.GetArtifact()).ToNot(BeNil())
- t.Expect(obj.GetArtifact().Checksum).To(Equal("bbdf96023c912c393b49d5238e227576ed0d20d1bb145d7476d817b80e20c11a"))
+ t.Expect(obj.GetArtifact().Checksum).To(Equal("5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3"))
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
t.Expect(obj.Status.URL).ToNot(BeEmpty())
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
@@ -1061,7 +1283,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
{
name: "Creates latest symlink to the created artifact",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
t.Expect(obj.GetArtifact()).ToNot(BeNil())
@@ -1185,6 +1407,105 @@ func TestHelmChartReconciler_getHelmRepositorySecret(t *testing.T) {
}
}
+func TestHelmChartReconciler_getVerificationKeyring(t *testing.T) {
+ secret := &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret",
+ Namespace: "foo",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: []byte("bar"),
+ },
+ }
+ clientBuilder := fake.NewClientBuilder()
+ clientBuilder.WithObjects(secret)
+
+ r := &HelmChartReconciler{
+ Client: clientBuilder.Build(),
+ }
+
+ tests := []struct {
+ name string
+ chart *sourcev1.HelmChart
+ want []byte
+ wantErr bool
+ }{
+ {
+ name: "Existing secret reference",
+ chart: &sourcev1.HelmChart{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: secret.Namespace,
+ },
+ Spec: sourcev1.HelmChartSpec{
+ VerificationKeyring: &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret",
+ },
+ Key: publicKeyFileName,
+ },
+ },
+ },
+ want: []byte("bar"),
+ },
+ {
+ name: "Empty secret reference",
+ chart: &sourcev1.HelmChart{
+ Spec: sourcev1.HelmChartSpec{
+ VerificationKeyring: nil,
+ },
+ },
+ want: nil,
+ },
+ {
+ name: "Error on client error",
+ chart: &sourcev1.HelmChart{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "different",
+ },
+ Spec: sourcev1.HelmChartSpec{
+ VerificationKeyring: &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret",
+ },
+ Key: publicKeyFileName,
+ },
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "Error on invalid key",
+ chart: &sourcev1.HelmChart{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "foo",
+ },
+ Spec: sourcev1.HelmChartSpec{
+ VerificationKeyring: &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret",
+ },
+ Key: "invalid-key",
+ },
+ },
+ },
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ got, err := r.getProvenanceKeyring(context.TODO(), tt.chart)
+ g.Expect(err != nil).To(Equal(tt.wantErr))
+ g.Expect(got).To(Equal(tt.want))
+ })
+ }
+}
+
func TestHelmChartReconciler_getSource(t *testing.T) {
mocks := []client.Object{
&sourcev1.HelmRepository{
@@ -1462,8 +1783,9 @@ func TestHelmChartReconciler_reconcileSubRecs(t *testing.T) {
}
}
-func mockChartBuild(name, version, path string) *chart.Build {
+func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
var copyP string
+ var copyPP string
if path != "" {
f, err := os.Open(path)
if err == nil {
@@ -1477,9 +1799,23 @@ func mockChartBuild(name, version, path string) *chart.Build {
}
}
}
+ if provFilePath != "" {
+ f, err := os.Open(provFilePath)
+ if err == nil {
+ defer f.Close()
+ ff, err := os.CreateTemp("", "chart-mock-*.tgz.prov")
+ if err == nil {
+ defer ff.Close()
+ if _, err = io.Copy(ff, f); err == nil {
+ copyPP = ff.Name()
+ }
+ }
+ }
+ }
return &chart.Build{
- Name: name,
- Version: version,
- Path: copyP,
+ Name: name,
+ Version: version,
+ Path: copyP,
+ ProvFilePath: copyPP,
}
}
diff --git a/controllers/testdata/charts/helmchart-0.1.0.tgz b/controllers/testdata/charts/helmchart-0.1.0.tgz
index f64a32eeeb54fc24a44390478a3adf5bcb5cf754..a43147fd87321deb4d03de1d89d5e16a7d4a6baf 100644
GIT binary patch
delta 3217
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
Da%W-z
delta 3214
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
A6951J
diff --git a/controllers/testdata/charts/helmchart-0.1.0.tgz.prov b/controllers/testdata/charts/helmchart-0.1.0.tgz.prov
new file mode 100644
index 000000000..df5962f5f
--- /dev/null
+++ b/controllers/testdata/charts/helmchart-0.1.0.tgz.prov
@@ -0,0 +1,26 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+apiVersion: v2
+appVersion: 1.16.0
+description: A Helm chart for Kubernetes
+name: helmchart
+type: application
+version: 0.1.0
+
+...
+files:
+ helmchart-0.1.0.tgz: sha256:5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3
+-----BEGIN PGP SIGNATURE-----
+
+wsDcBAEBCgAQBQJiJkq8CRBwNbqX0yqHwQAAJ5sMAKTMqzOLvrunXBQ8TPXcqXGc
+bzZ9MSrK3mBzQlhZHxabCZP25vemUwGsvNyS2BS8ow+dDzaug8luZBw1aC5slSyS
+uAG9bkcyqHwsHnJ+Z8O5cpYsmOzhA4CDSsq1xICxCKYy2jZPj+I8KBk5INtSi6gP
+lB1YV4ry42D2BsxK7IuPr5iUzecsVNsMbvupVLUSqsR3k3A2plGhBw10yCnYxEFg
+MYlUdhHKFOSiUEmgif7toEQFfofxoutcPaAHe1zRYE4t1M2AozGx3njXchTdkHbe
+WvsNc96wrFAGu852VXC6hyJH2keWhY91vaoELVbkDYnCiHouu/yXE4ox2MgN6Kr0
+u1gNeaEtk+IxYthDuBBRfslQ6O6lIfi3vObanC6Wl1pmC3YyYtnFOz6VQzW9k+Sv
+FF9l6ysxoYxGWzAmIhGDIoohKwD1LtRBDjWHPivsOkYyEjmFb0MUsgFHuhVRMV4Z
+aTckCGBebQS0bR3wE4GaGV1sLVoZDXph7v7YGa7Enw==
+=7DS/
+-----END PGP SIGNATURE-----
\ No newline at end of file
diff --git a/controllers/testdata/charts/pub.gpg b/controllers/testdata/charts/pub.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..291f8abd8b4f524a544bdbe21cf0e1212ce010b1
GIT binary patch
literal 1728
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
literal 0
HcmV?d00001
From cef5beed3b1b1acdfacee4d2314e393136594330 Mon Sep 17 00:00:00 2001
From: Sanskar Jaiswal
Date: Mon, 14 Mar 2022 20:59:56 +0530
Subject: [PATCH 05/10] refactor code to be more neat
Signed-off-by: Sanskar Jaiswal
---
api/go.mod | 5 +-
api/go.sum | 15 +-
controllers/helmchart_controller.go | 68 ++--
controllers/helmchart_controller_test.go | 352 ++++++++----------
controllers/storage.go | 31 +-
.../testdata/charts/helmchart-0.1.0.tgz | Bin 3280 -> 3416 bytes
.../testdata/charts/helmchart-0.1.0.tgz.prov | 22 +-
internal/helm/chart/builder.go | 10 +-
internal/helm/chart/builder_local.go | 39 +-
internal/helm/chart/builder_local_test.go | 42 ++-
internal/helm/chart/builder_remote.go | 35 +-
internal/helm/chart/builder_remote_test.go | 39 +-
internal/helm/chart/verify.go | 56 ++-
internal/helm/repository/chart_repository.go | 2 +-
internal/util/file.go | 43 +++
internal/util/temp.go | 26 --
16 files changed, 454 insertions(+), 331 deletions(-)
create mode 100644 internal/util/file.go
diff --git a/api/go.mod b/api/go.mod
index a5445cc68..7ceae5b8a 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -17,10 +17,13 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 // indirect
+ golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect
+ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+ k8s.io/api v0.23.4 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
diff --git a/api/go.sum b/api/go.sum
index 0526ae80d..1c0d21c48 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -297,6 +297,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -334,7 +336,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -569,8 +570,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I=
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE=
+golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -660,8 +662,9 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk=
golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -857,8 +860,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -893,8 +897,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro=
k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg=
+k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E=
+k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI=
k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4=
k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc=
k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM=
diff --git a/controllers/helmchart_controller.go b/controllers/helmchart_controller.go
index 3c700830b..ddd201d3c 100644
--- a/controllers/helmchart_controller.go
+++ b/controllers/helmchart_controller.go
@@ -73,6 +73,7 @@ var helmChartReadyCondition = summarize.Conditions{
sourcev1.FetchFailedCondition,
sourcev1.StorageOperationFailedCondition,
sourcev1.ArtifactOutdatedCondition,
+ sourcev1.SourceVerifiedCondition,
meta.ReadyCondition,
meta.ReconcilingCondition,
meta.StalledCondition,
@@ -82,6 +83,7 @@ var helmChartReadyCondition = summarize.Conditions{
sourcev1.FetchFailedCondition,
sourcev1.StorageOperationFailedCondition,
sourcev1.ArtifactOutdatedCondition,
+ sourcev1.SourceVerifiedCondition,
meta.StalledCondition,
meta.ReconcilingCondition,
},
@@ -469,16 +471,20 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
opts.VersionMetadata = strconv.FormatInt(obj.Generation, 10)
}
- var keyring []byte
- keyring, err = r.getProvenanceKeyring(ctx, obj)
+ keyring, err := r.getProvenanceKeyring(ctx, obj)
if err != nil {
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, err.Error())
- return sreconcile.ResultEmpty, err
+ e := &serror.Event{
+ Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
+ Reason: sourcev1.SourceVerifiedCondition,
+ }
+ conditions.MarkFalse(obj, sourcev1.FetchFailedCondition, sourcev1.SourceVerifiedCondition, e.Error())
+ return sreconcile.ResultEmpty, e
}
+ opts.Keyring = keyring
// Build the chart
ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version}
- build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts, keyring)
+ build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts)
if err != nil {
return sreconcile.ResultEmpty, err
@@ -599,19 +605,23 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
}
opts.VersionMetadata += strconv.FormatInt(obj.Generation, 10)
}
- var keyring []byte
- keyring, err = r.getProvenanceKeyring(ctx, obj)
+ keyring, err := r.getProvenanceKeyring(ctx, obj)
if err != nil {
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, err.Error())
- return sreconcile.ResultEmpty, err
+ e := &serror.Event{
+ Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
+ Reason: sourcev1.SourceVerifiedCondition,
+ }
+ conditions.MarkFalse(obj, sourcev1.FetchFailedCondition, sourcev1.SourceVerifiedCondition, e.Error())
+ return sreconcile.ResultEmpty, e
}
+ opts.Keyring = keyring
// Build chart
cb := chart.NewLocalBuilder(dm)
build, err := cb.Build(ctx, chart.LocalReference{
WorkDir: sourceDir,
Path: chartPath,
- }, util.TempPathForObj("", ".tgz", obj), opts, keyring)
+ }, util.TempPathForObj("", ".tgz", obj), opts)
if err != nil {
return sreconcile.ResultEmpty, err
}
@@ -641,6 +651,14 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
conditions.MarkTrue(obj, meta.ReadyCondition, reasonForBuild(b), b.Summary())
}
+ if b.VerificationSignature != nil && b.ProvFilePath != "" && obj.GetArtifact() != nil {
+ var sigVerMsg strings.Builder
+ sigVerMsg.WriteString(fmt.Sprintf("chart signed by: %v", strings.Join(b.VerificationSignature.Identities[:], ",")))
+ sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: %X", b.VerificationSignature.KeyFingerprint))
+ sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: %s", b.VerificationSignature.FileHash))
+
+ conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, reasonForBuild(b), sigVerMsg.String())
+ }
}()
// Create artifact from build data
@@ -692,7 +710,7 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
if err = r.Storage.CopyFromPath(&provArtifact, b.ProvFilePath); err != nil {
return sreconcile.ResultEmpty, &serror.Event{
Err: fmt.Errorf("unable to copy Helm chart provenance file to storage: %w", err),
- Reason: sourcev1.StorageOperationFailedReason,
+ Reason: sourcev1.StorageOperationFailedCondition,
}
}
}
@@ -790,15 +808,23 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
obj.Status.Artifact = nil
return nil
}
+
if obj.GetArtifact() != nil {
- if deleted, err := r.Storage.RemoveAllButCurrent(*obj.GetArtifact()); err != nil {
+ localPath := r.Storage.LocalPath(*obj.GetArtifact())
+ provFilePath := localPath + ".prov"
+ dir := filepath.Dir(localPath)
+ callbacks := make([]func(path string, info os.FileInfo) bool, 0)
+ callbacks = append(callbacks, func(path string, info os.FileInfo) bool {
+ if path != localPath && path != provFilePath && info.Mode()&os.ModeSymlink != os.ModeSymlink {
+ return true
+ }
+ return false
+ })
+ if _, err := r.Storage.RemoveConditionally(dir, callbacks); err != nil {
return &serror.Event{
Err: fmt.Errorf("garbage collection of old artifacts failed: %w", err),
Reason: "GarbageCollectionFailed",
}
- } else if len(deleted) > 0 {
- r.eventLogf(ctx, obj, events.EventTypeTrace, "GarbageCollectionSucceeded",
- "garbage collected old artifacts")
}
}
return nil
@@ -1076,20 +1102,12 @@ func (r *HelmChartReconciler) getProvenanceKeyring(ctx context.Context, chart *s
var secret corev1.Secret
err := r.Client.Get(ctx, name, &secret)
if err != nil {
- e := &serror.Event{
- Err: fmt.Errorf("failed to get secret '%s': %w", chart.Spec.VerificationKeyring.SecretRef.Name, err),
- Reason: sourcev1.AuthenticationFailedReason,
- }
- return nil, e
+ return nil, err
}
key := chart.Spec.VerificationKeyring.Key
if val, ok := secret.Data[key]; !ok {
err = fmt.Errorf("secret doesn't contain the advertised verification keyring name %s", key)
- e := &serror.Event{
- Err: fmt.Errorf("invalid secret '%s': %w", secret.GetName(), err),
- Reason: sourcev1.AuthenticationFailedReason,
- }
- return nil, e
+ return nil, err
} else {
return val, nil
}
diff --git a/controllers/helmchart_controller_test.go b/controllers/helmchart_controller_test.go
index 5390f57a4..37b08630f 100644
--- a/controllers/helmchart_controller_test.go
+++ b/controllers/helmchart_controller_test.go
@@ -67,7 +67,119 @@ func TestHelmChartReconciler_Reconcile(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(server.Root())
+ g.Expect(server.PackageChartWithVersion(chartPath, chartVersion)).To(Succeed())
+ g.Expect(server.GenerateIndex()).To(Succeed())
+
+ server.Start()
+ defer server.Stop()
+
+ ns, err := testEnv.CreateNamespace(ctx, "helmchart")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ repository := &sourcev1.HelmRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "helmrepository-",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.HelmRepositorySpec{
+ URL: server.URL(),
+ },
+ }
+ g.Expect(testEnv.CreateAndWait(ctx, repository)).To(Succeed())
+
+ obj := &sourcev1.HelmChart{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "helmrepository-reconcile-",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.HelmChartSpec{
+ Chart: chartName,
+ Version: chartVersion,
+ SourceRef: sourcev1.LocalHelmChartSourceReference{
+ Kind: sourcev1.HelmRepositoryKind,
+ Name: repository.Name,
+ },
+ },
+ }
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+
+ // Wait for finalizer to be set
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ return len(obj.Finalizers) > 0
+ }, timeout).Should(BeTrue())
+
+ // Wait for HelmChart to be Ready
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ if !conditions.IsReady(obj) || obj.Status.Artifact == nil {
+ return false
+ }
+ readyCondition := conditions.Get(obj, meta.ReadyCondition)
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ obj.Generation == obj.Status.ObservedGeneration
+ }, timeout).Should(BeTrue())
+
+ // Check if the object status is valid.
+ condns := &status.Conditions{NegativePolarity: helmChartReadyCondition.NegativePolarity}
+ checker := status.NewChecker(testEnv.Client, testEnv.GetScheme(), condns)
+ checker.CheckErr(ctx, obj)
+
+ // kstatus client conformance check.
+ u, err := patch.ToUnstructured(obj)
+ g.Expect(err).ToNot(HaveOccurred())
+ res, err := kstatus.Compute(u)
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(res.Status).To(Equal(kstatus.CurrentStatus))
+
+ // Patch the object with reconcile request annotation.
+ patchHelper, err := patch.NewHelper(obj, testEnv.Client)
+ g.Expect(err).ToNot(HaveOccurred())
+ annotations := map[string]string{
+ meta.ReconcileRequestAnnotation: "now",
+ }
+ obj.SetAnnotations(annotations)
+ g.Expect(patchHelper.Patch(ctx, obj)).ToNot(HaveOccurred())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ return obj.Status.LastHandledReconcileAt == "now"
+ }, timeout).Should(BeTrue())
+
+ g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
+
+ // Wait for HelmChart to be deleted
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return apierrors.IsNotFound(err)
+ }
+ return false
+ }, timeout).Should(BeTrue())
+}
+
+func TestHelmChartReconciler_ReconcileWithSigVerification(t *testing.T) {
+ g := NewWithT(t)
+
+ const (
+ chartName = "helmchart"
+ chartVersion = "0.2.0"
+ chartPath = "testdata/charts/helmchart"
+ )
+
+ server, err := helmtestserver.NewTempHelmServer()
+ g.Expect(err).NotTo(HaveOccurred())
+ defer os.RemoveAll(server.Root())
+
publicKeyPath := fmt.Sprintf("%s/%s", server.Root(), publicKeyFileName)
+
g.Expect(server.PackageSignedChartWithVersion(chartPath, chartVersion, publicKeyPath)).To(Succeed())
g.Expect(server.GenerateIndex()).To(Succeed())
@@ -215,7 +327,6 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) {
if err := testStorage.MkdirAll(*obj.Status.Artifact); err != nil {
return err
}
-
if err := testStorage.AtomicWriteFile(obj.Status.Artifact, strings.NewReader(v), 0644); err != nil {
return err
}
@@ -347,13 +458,9 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
}
g.Expect(storage.Archive(gitArtifact, "testdata/charts", nil)).To(Succeed())
- keyring, err := os.ReadFile("testdata/charts/pub.gpg")
- g.Expect(err).ToNot(HaveOccurred())
-
tests := []struct {
name string
source sourcev1.Source
- secret *corev1.Secret
beforeFunc func(obj *sourcev1.HelmChart)
want sreconcile.Result
wantErr error
@@ -371,27 +478,12 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
Artifact: gitArtifact,
},
},
- secret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret",
- Namespace: "default",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring,
- },
- },
beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
obj.Spec.SourceRef = sourcev1.LocalHelmChartSourceReference{
Name: "gitrepository",
Kind: sourcev1.GitRepositoryKind,
}
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret",
- },
- Key: publicKeyFileName,
- }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, build chart.Build, obj sourcev1.HelmChart) {
@@ -399,7 +491,6 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
g.Expect(build.Name).To(Equal("helmchart"))
g.Expect(build.Version).To(Equal("0.1.0"))
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).To(BeARegularFile())
g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal(gitArtifact.Revision))
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
@@ -408,7 +499,6 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
- g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
@@ -515,9 +605,6 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
if tt.source != nil {
clientBuilder.WithRuntimeObjects(tt.source)
}
- if tt.secret != nil {
- clientBuilder.WithRuntimeObjects(tt.secret)
- }
r := &HelmChartReconciler{
Client: clientBuilder.Build(),
@@ -568,58 +655,33 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
)
serverFactory, err := helmtestserver.NewTempHelmServer()
- publicKeyPath := fmt.Sprintf("%s/%s", serverFactory.Root(), publicKeyFileName)
-
g.Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(serverFactory.Root())
for _, ver := range []string{chartVersion, higherChartVersion} {
- g.Expect(serverFactory.PackageSignedChartWithVersion(chartPath, ver, publicKeyPath+ver)).To(Succeed())
+ g.Expect(serverFactory.PackageChartWithVersion(chartPath, ver)).To(Succeed())
}
g.Expect(serverFactory.GenerateIndex()).To(Succeed())
- keyring1, err := os.ReadFile(publicKeyPath + chartVersion)
- g.Expect(err).ToNot(HaveOccurred())
- defer os.Remove(publicKeyPath + chartVersion)
-
- keyring2, err := os.ReadFile(publicKeyPath + higherChartVersion)
- g.Expect(err).ToNot(HaveOccurred())
- defer os.Remove(publicKeyPath + higherChartVersion)
-
type options struct {
username string
password string
}
tests := []struct {
- name string
- server options
- secret *corev1.Secret
- keyringSecret *corev1.Secret
- beforeFunc func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository)
- want sreconcile.Result
- wantErr error
- assertFunc func(g *WithT, obj *sourcev1.HelmChart, build chart.Build)
- cleanFunc func(g *WithT, build *chart.Build)
+ name string
+ server options
+ secret *corev1.Secret
+ beforeFunc func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository)
+ want sreconcile.Result
+ wantErr error
+ assertFunc func(g *WithT, obj *sourcev1.HelmChart, build chart.Build)
+ cleanFunc func(g *WithT, build *chart.Build)
}{
{
name: "Reconciles chart build",
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Spec.Chart = "helmchart"
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret-0.3.0",
- },
- Key: publicKeyFileName,
- }
- },
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret-0.3.0",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring2,
- },
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
@@ -627,12 +689,9 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(higherChartVersion))
g.Expect(build.Path).ToNot(BeEmpty())
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).ToNot(BeEmpty())
- g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
- g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
@@ -641,14 +700,6 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
username: "foo",
password: "bar",
},
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret-0.2.0",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring1,
- },
- },
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "auth",
@@ -662,12 +713,6 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
obj.Spec.Chart = chartName
obj.Spec.Version = chartVersion
repository.Spec.SecretRef = &meta.LocalObjectReference{Name: "auth"}
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret-0.2.0",
- },
- Key: publicKeyFileName,
- }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
@@ -675,34 +720,17 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(chartVersion))
g.Expect(build.Path).ToNot(BeEmpty())
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).ToNot(BeEmpty())
- g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
- g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
name: "Uses artifact as build cache",
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret-0.2.0",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring1,
- },
- },
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Spec.Chart = chartName
obj.Spec.Version = chartVersion
obj.Status.Artifact = &sourcev1.Artifact{Path: chartName + "-" + chartVersion + ".tgz"}
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret-0.2.0",
- },
- Key: publicKeyFileName,
- }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
@@ -710,30 +738,14 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(chartVersion))
g.Expect(build.Path).To(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path)))
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).To(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path+".prov")))
- g.Expect(build.ProvFilePath).To(BeARegularFile())
},
},
{
name: "Sets Generation as VersionMetadata with values files",
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret-0.3.0",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring2,
- },
- },
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Spec.Chart = chartName
obj.Generation = 3
obj.Spec.ValuesFiles = []string{"values.yaml", "override.yaml"}
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret-0.3.0",
- },
- Key: publicKeyFileName,
- }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
@@ -741,24 +753,13 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(higherChartVersion + "+3"))
g.Expect(build.Path).ToNot(BeEmpty())
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).ToNot(BeEmpty())
- g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
- g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
name: "Forces build on generation change",
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret-0.2.0",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring1,
- },
- },
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
obj.Generation = 3
obj.Spec.Chart = chartName
@@ -766,12 +767,6 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
obj.Status.ObservedGeneration = 2
obj.Status.Artifact = &sourcev1.Artifact{Path: chartName + "-" + chartVersion + ".tgz"}
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret-0.2.0",
- },
- Key: publicKeyFileName,
- }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, obj *sourcev1.HelmChart, build chart.Build) {
@@ -779,12 +774,9 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
g.Expect(build.Version).To(Equal(chartVersion))
g.Expect(build.Path).ToNot(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path)))
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).ToNot(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path+".prov")))
- g.Expect(build.ProvFilePath).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
g.Expect(os.Remove(build.Path)).To(Succeed())
- g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
},
},
{
@@ -868,9 +860,6 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
if tt.secret != nil {
clientBuilder.WithObjects(tt.secret.DeepCopy())
}
- if tt.keyringSecret != nil {
- clientBuilder.WithObjects(tt.keyringSecret.DeepCopy())
- }
storage, err := newTestStorage(server)
g.Expect(err).ToNot(HaveOccurred())
@@ -912,9 +901,6 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
defer tt.cleanFunc(g, &b)
}
got, err := r.buildFromHelmRepository(context.TODO(), obj, repository, &b)
- if err != nil {
- t.Logf("error: %s", err)
- }
g.Expect(err != nil).To(Equal(tt.wantErr != nil))
if tt.wantErr != nil {
@@ -952,28 +938,18 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
g.Expect(storage.CopyFromPath(yamlArtifact, "testdata/charts/helmchart/values.yaml")).To(Succeed())
cachedArtifact := &sourcev1.Artifact{
Revision: "0.1.0",
- Path: "helmchart-0.1.0.tgz",
+ Path: "cached.tgz",
}
g.Expect(storage.CopyFromPath(cachedArtifact, "testdata/charts/helmchart-0.1.0.tgz")).To(Succeed())
- provArtifact := &sourcev1.Artifact{
- Revision: "1234smth",
- Path: "helmchart-0.1.0.tgz.prov",
- }
- g.Expect(storage.CopyFromPath(provArtifact, "testdata/charts/helmchart-0.1.0.tgz.prov")).To(Succeed())
-
- keyring, err := os.ReadFile("testdata/charts/pub.gpg")
- g.Expect(err).ToNot(HaveOccurred())
-
tests := []struct {
- name string
- source sourcev1.Artifact
- keyringSecret *corev1.Secret
- beforeFunc func(obj *sourcev1.HelmChart)
- want sreconcile.Result
- wantErr error
- assertFunc func(g *WithT, build chart.Build)
- cleanFunc func(g *WithT, build *chart.Build)
+ name string
+ source sourcev1.Artifact
+ beforeFunc func(obj *sourcev1.HelmChart)
+ want sreconcile.Result
+ wantErr error
+ assertFunc func(g *WithT, build chart.Build)
+ cleanFunc func(g *WithT, build *chart.Build)
}{
{
name: "Resolves chart dependencies and builds",
@@ -1037,24 +1013,9 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
{
name: "Chart from storage cache",
source: *chartsArtifact.DeepCopy(),
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret",
- Namespace: "default",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring,
- },
- },
beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
obj.Status.Artifact = cachedArtifact.DeepCopy()
- obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
- SecretRef: meta.LocalObjectReference{
- Name: "keyring-secret",
- },
- Key: publicKeyFileName,
- }
},
want: sreconcile.ResultSuccess,
assertFunc: func(g *WithT, build chart.Build) {
@@ -1062,21 +1023,11 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
g.Expect(build.Version).To(Equal("0.1.0"))
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
g.Expect(build.Path).To(BeARegularFile())
- g.Expect(build.ProvFilePath).To(Equal(storage.LocalPath(*provArtifact.DeepCopy())))
- g.Expect(build.ProvFilePath).To(BeARegularFile())
},
},
{
name: "Generation change forces rebuild",
source: *chartsArtifact.DeepCopy(),
- keyringSecret: &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "keyring-secret",
- },
- Data: map[string][]byte{
- publicKeyFileName: keyring,
- },
- },
beforeFunc: func(obj *sourcev1.HelmChart) {
obj.Generation = 2
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
@@ -1117,13 +1068,8 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
- clientBuilder := fake.NewClientBuilder()
- if tt.keyringSecret != nil {
- clientBuilder.WithObjects(tt.keyringSecret.DeepCopy())
- }
-
r := &HelmChartReconciler{
- Client: clientBuilder.Build(),
+ Client: fake.NewClientBuilder().Build(),
EventRecorder: record.NewFakeRecorder(32),
Storage: storage,
Getters: testGetters,
@@ -1189,7 +1135,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
t.Expect(obj.GetArtifact()).ToNot(BeNil())
- t.Expect(obj.GetArtifact().Checksum).To(Equal("5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3"))
+ t.Expect(obj.GetArtifact().Checksum).To(Equal("007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a"))
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
t.Expect(obj.Status.URL).ToNot(BeEmpty())
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
@@ -1200,24 +1146,21 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
},
{
- name: "A build with a non-nil ProvFilePath persists prov file to storage",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", "testdata/charts/helmchart-0.1.0.tgz"),
+ name: "Build with a verified signature sets SourceVerifiedCondition=Truue",
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", "testdata/charts/helmchart-0.1.0.tgz.prov"),
beforeFunc: func(obj *sourcev1.HelmChart) {
- conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
+ obj.Status.Artifact = &sourcev1.Artifact{
+ Path: "testdata/charts/helmchart-0.1.0.tgz",
+ }
},
+ want: sreconcile.ResultSuccess,
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
- provArtifact := testStorage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "0.1.0", fmt.Sprintf("%s-%s.tgz.prov", "helmchart", "0.1.0"))
+ provArtifact := testStorage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "0.1.0", "helmchart-0.1.0.tgz.prov")
t.Expect(provArtifact.Path).ToNot(BeEmpty())
- t.Expect(obj.GetArtifact()).ToNot(BeNil())
- fmt.Printf("checksum: %s", obj.GetArtifact().Checksum)
- t.Expect(obj.GetArtifact().Checksum).To(Equal("5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3"))
- t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
- t.Expect(obj.Status.URL).ToNot(BeEmpty())
- t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
},
- want: sreconcile.ResultSuccess,
assertConditions: []metav1.Condition{
*conditions.TrueCondition(meta.ReadyCondition, sourcev1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartPullSucceededReason, "chart signed by: TestUser1,TestUser2 using key with fingeprint: 0102000000000000000000000000000000000000 and hash verified: 53gntj23r24asnf0"),
},
},
{
@@ -1271,7 +1214,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
t.Expect(obj.GetArtifact()).ToNot(BeNil())
- t.Expect(obj.GetArtifact().Checksum).To(Equal("5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3"))
+ t.Expect(obj.GetArtifact().Checksum).To(Equal("007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a"))
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
t.Expect(obj.Status.URL).ToNot(BeEmpty())
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
@@ -1786,6 +1729,7 @@ func TestHelmChartReconciler_reconcileSubRecs(t *testing.T) {
func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
var copyP string
var copyPP string
+ var verSig *chart.VerificationSignature
if path != "" {
f, err := os.Open(path)
if err == nil {
@@ -1810,12 +1754,18 @@ func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
copyPP = ff.Name()
}
}
+ verSig = &chart.VerificationSignature{
+ FileHash: "53gntj23r24asnf0",
+ Identities: []string{"TestUser1", "TestUser2"},
+ KeyFingerprint: [20]byte{1, 2},
+ }
}
}
return &chart.Build{
- Name: name,
- Version: version,
- Path: copyP,
- ProvFilePath: copyPP,
+ Name: name,
+ Version: version,
+ Path: copyP,
+ ProvFilePath: copyPP,
+ VerificationSignature: verSig,
}
}
diff --git a/controllers/storage.go b/controllers/storage.go
index f8016f5fe..bd39b66b4 100644
--- a/controllers/storage.go
+++ b/controllers/storage.go
@@ -120,7 +120,6 @@ func (s *Storage) RemoveAll(artifact sourcev1.Artifact) (string, error) {
func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, error) {
deletedFiles := []string{}
localPath := s.LocalPath(artifact)
- localProvPath := localPath + ".prov"
dir := filepath.Dir(localPath)
var errors []string
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
@@ -129,7 +128,7 @@ func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, err
return nil
}
- if path != localPath && path != localProvPath && !info.IsDir() && info.Mode()&os.ModeSymlink != os.ModeSymlink {
+ if path != localPath && !info.IsDir() && info.Mode()&os.ModeSymlink != os.ModeSymlink {
if err := os.Remove(path); err != nil {
errors = append(errors, info.Name())
} else {
@@ -146,6 +145,34 @@ func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, err
return deletedFiles, nil
}
+func (s *Storage) RemoveConditionally(dir string, callbacks []func(path string, info os.FileInfo) bool) ([]string, error) {
+ deletedFiles := []string{}
+ var errors []string
+ _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ errors = append(errors, err.Error())
+ return nil
+ }
+ for _, callback := range callbacks {
+ if callback(path, info) {
+ if err := os.Remove(path); err != nil {
+ errors = append(errors, info.Name())
+ } else {
+ // Collect the successfully deleted file paths.
+ deletedFiles = append(deletedFiles, path)
+ }
+ break
+ }
+ }
+ return nil
+ })
+
+ if len(errors) > 0 {
+ return deletedFiles, fmt.Errorf("failed to remove files: %s", strings.Join(errors, " "))
+ }
+ return deletedFiles, nil
+}
+
// ArtifactExist returns a boolean indicating whether the v1beta1.Artifact exists in storage and is a regular file.
func (s *Storage) ArtifactExist(artifact sourcev1.Artifact) bool {
fi, err := os.Lstat(s.LocalPath(artifact))
diff --git a/controllers/testdata/charts/helmchart-0.1.0.tgz b/controllers/testdata/charts/helmchart-0.1.0.tgz
index a43147fd87321deb4d03de1d89d5e16a7d4a6baf..186a1ddb788b83cff243f8ffa684859c1700a8b1 100644
GIT binary patch
delta 3390
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
U2M+`P7XSeN|39#41^`?D02V^B2><{9
delta 3253
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
diff --git a/controllers/testdata/charts/helmchart-0.1.0.tgz.prov b/controllers/testdata/charts/helmchart-0.1.0.tgz.prov
index df5962f5f..7c44d8c25 100644
--- a/controllers/testdata/charts/helmchart-0.1.0.tgz.prov
+++ b/controllers/testdata/charts/helmchart-0.1.0.tgz.prov
@@ -10,17 +10,17 @@ version: 0.1.0
...
files:
- helmchart-0.1.0.tgz: sha256:5fabb8b212945e7187a24a5e893d06fe98c83f3c49ed0f7b6df6de633d95a8f3
+ helmchart-0.1.0.tgz: sha256:007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a
-----BEGIN PGP SIGNATURE-----
-wsDcBAEBCgAQBQJiJkq8CRBwNbqX0yqHwQAAJ5sMAKTMqzOLvrunXBQ8TPXcqXGc
-bzZ9MSrK3mBzQlhZHxabCZP25vemUwGsvNyS2BS8ow+dDzaug8luZBw1aC5slSyS
-uAG9bkcyqHwsHnJ+Z8O5cpYsmOzhA4CDSsq1xICxCKYy2jZPj+I8KBk5INtSi6gP
-lB1YV4ry42D2BsxK7IuPr5iUzecsVNsMbvupVLUSqsR3k3A2plGhBw10yCnYxEFg
-MYlUdhHKFOSiUEmgif7toEQFfofxoutcPaAHe1zRYE4t1M2AozGx3njXchTdkHbe
-WvsNc96wrFAGu852VXC6hyJH2keWhY91vaoELVbkDYnCiHouu/yXE4ox2MgN6Kr0
-u1gNeaEtk+IxYthDuBBRfslQ6O6lIfi3vObanC6Wl1pmC3YyYtnFOz6VQzW9k+Sv
-FF9l6ysxoYxGWzAmIhGDIoohKwD1LtRBDjWHPivsOkYyEjmFb0MUsgFHuhVRMV4Z
-aTckCGBebQS0bR3wE4GaGV1sLVoZDXph7v7YGa7Enw==
-=7DS/
+wsDcBAEBCgAQBQJiKwNBCRBwNbqX0yqHwQAACj8MABCY6mVrWaJdC64PbhTTonVE
+97MZZpQBT+CZIRAecfkvcTeMTBeKh/yRwsSmjwo46eKOpNFJ1eQVHqVcKWLfBn3Z
+AijuXTaISl8SnQyKPF2Z8n+YrYwh9OWPUX2CpUQstx+snSLDuv5ltWIgRlzfHAUN
+hwzsgjs8bpHe8wZTgnASUVbcMMYQXCcovbXB6NATDLkZLHBWWEISicOl6VYLLl2D
+kZg7LDcDKPcPmKJ6WtVurkyWXhK3jdYzlaOQWjs2nLIH/CdlmAygELuWexsOZAhY
+MEauKEMoVzDQF5oaNA78AzlBLGogxao5fBYtAAHGb5tQdnVRUeSci+7IR0LHsS05
+YF/UnUF69GSESfoKIBvQuzex4BRCLBwayq6CSyrpZQ2+Vg4ARPo7LFg7Wy0zvC9Z
+NxGnIeh1az9hltdzPgg6ZahPZB+eMF+t9ouAz9OZ3kxYUDmoE+Z+NqRWsPi27Cxk
+CSw9EfJfDsputN/wj4NAxZKfqauMtS5sgaSgtrW+zA==
+=mfBq
-----END PGP SIGNATURE-----
\ No newline at end of file
diff --git a/internal/helm/chart/builder.go b/internal/helm/chart/builder.go
index 25bc19563..50fa9ffc5 100644
--- a/internal/helm/chart/builder.go
+++ b/internal/helm/chart/builder.go
@@ -85,7 +85,7 @@ type Builder interface {
// Reference and BuildOptions, and writes it to p.
// It returns the Build result, or an error.
// It may return an error for unsupported Reference implementations.
- Build(ctx context.Context, ref Reference, p string, opts BuildOptions, keyring []byte) (*Build, error)
+ Build(ctx context.Context, ref Reference, p string, opts BuildOptions) (*Build, error)
}
// BuildOptions provides a list of options for Builder.Build.
@@ -104,6 +104,10 @@ type BuildOptions struct {
// Force can be set to force the build of the chart, for example
// because the list of ValuesFiles has changed.
Force bool
+
+ // Keyring can be set to the data of the chart VerificationKeyring secret
+ // used for verifying a chart's signature using a provenance file.
+ Keyring []byte
}
// GetValuesFiles returns BuildOptions.ValuesFiles, except if it equals
@@ -129,6 +133,9 @@ type Build struct {
// Can be empty, in which case it should be assumed that the packaged
// chart is not verified.
ProvFilePath string
+ // VerificationSignature is populated when a chart's signature
+ // is susccessfully verified using it's provenance file.
+ VerificationSignature *VerificationSignature
// ValuesFiles is the list of files used to compose the chart's
// default "values.yaml".
ValuesFiles []string
@@ -161,7 +168,6 @@ func (b *Build) Summary() string {
if len(b.ValuesFiles) > 0 {
s.WriteString(fmt.Sprintf(" and merged values files %v", b.ValuesFiles))
}
-
return s.String()
}
diff --git a/internal/helm/chart/builder_local.go b/internal/helm/chart/builder_local.go
index cbbe840b8..6885e1d40 100644
--- a/internal/helm/chart/builder_local.go
+++ b/internal/helm/chart/builder_local.go
@@ -26,6 +26,7 @@ import (
"github.com/Masterminds/semver/v3"
securejoin "github.com/cyphar/filepath-securejoin"
"helm.sh/helm/v3/pkg/chart/loader"
+ "helm.sh/helm/v3/pkg/provenance"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/runtime/transform"
@@ -63,7 +64,7 @@ func NewLocalBuilder(dm *DependencyManager) Builder {
// If the LocalReference.Path refers to a chart directory, dependencies are
// confirmed to be present using the DependencyManager, while attempting to
// resolve any missing.
-func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string, opts BuildOptions, keyring []byte) (*Build, error) {
+func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string, opts BuildOptions) (*Build, error) {
localRef, ok := ref.(LocalReference)
if !ok {
err := fmt.Errorf("expected local chart reference")
@@ -105,37 +106,43 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
isChartDir := pathIsDir(localRef.Path)
requiresPackaging := isChartDir || opts.VersionMetadata != "" || len(opts.GetValuesFiles()) != 0
- // If all the following is true, we do not need to package the chart:
- // - Chart name from cached chart matches resolved name
- // - Chart version from cached chart matches calculated version
- // - BuildOptions.Force is False
var provFilePath string
- verifyProvFile := func(chart, provFile string) error {
- if keyring != nil {
+ verifyProvFile := func(chart, provFile string) (*provenance.Verification, error) {
+ if opts.Keyring != nil {
if _, err := os.Stat(provFile); err != nil {
err = fmt.Errorf("could not load provenance file %s: %w", provFile, err)
- return &BuildError{Reason: ErrProvenanceVerification, Err: err}
+ return nil, &BuildError{Reason: ErrProvenanceVerification, Err: err}
}
- err := VerifyProvenanceFile(bytes.NewReader(keyring), chart, provFile)
+ ver, err := verifyChartWithProvFile(bytes.NewReader(opts.Keyring), chart, provFile)
if err != nil {
err = fmt.Errorf("failed to verify helm chart using provenance file: %w", err)
- return &BuildError{Reason: ErrProvenanceVerification, Err: err}
+ return nil, &BuildError{Reason: ErrProvenanceVerification, Err: err}
}
+ return ver, nil
}
- return nil
+ return nil, nil
}
+
+ // If all the following is true, we do not need to package the chart:
+ // - Chart name from cached chart matches resolved name
+ // - Chart version from cached chart matches calculated version
+ // - BuildOptions.Force is False
if opts.CachedChart != "" && !opts.Force {
if curMeta, err = LoadChartMetadataFromArchive(opts.CachedChart); err == nil {
// If the cached metadata is corrupt, we ignore its existence
// and continue the build
if err = curMeta.Validate(); err == nil {
if result.Name == curMeta.Name && result.Version == curMeta.Version {
+ // We can only verify a cached chart with provenance file if we didn't
+ // package the chart ourselves, and instead stored it as is.
if !requiresPackaging {
provFilePath = provenanceFilePath(opts.CachedChart)
- if err = verifyProvFile(opts.CachedChart, provFilePath); err != nil {
+ if ver, err := verifyProvFile(opts.CachedChart, provFilePath); err != nil {
return nil, err
+ } else {
+ result.VerificationSignature = buildVerificationSig(ver)
+ result.ProvFilePath = provFilePath
}
- result.ProvFilePath = provFilePath
}
result.Path = opts.CachedChart
result.ValuesFiles = opts.GetValuesFiles()
@@ -156,11 +163,13 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
if err = copyFileToPath(provenanceFilePath(localRef.Path), provFilePath); err != nil {
return result, &BuildError{Reason: ErrChartPull, Err: err}
}
- if err = verifyProvFile(localRef.Path, provFilePath); err != nil {
+ if ver, err := verifyProvFile(localRef.Path, provFilePath); err != nil {
return result, err
+ } else {
+ result.ProvFilePath = provFilePath
+ result.VerificationSignature = buildVerificationSig(ver)
}
result.Path = p
- result.ProvFilePath = provFilePath
return result, nil
}
diff --git a/internal/helm/chart/builder_local_test.go b/internal/helm/chart/builder_local_test.go
index e108250c3..a3543a605 100644
--- a/internal/helm/chart/builder_local_test.go
+++ b/internal/helm/chart/builder_local_test.go
@@ -110,6 +110,7 @@ func TestLocalBuilder_Build(t *testing.T) {
{
name: "already packaged chart",
reference: LocalReference{Path: "./../testdata/charts/helmchart-0.1.0.tgz"},
+ buildOpts: BuildOptions{Keyring: keyring},
wantVersion: "0.1.0",
wantPackaged: false,
},
@@ -215,7 +216,7 @@ fullnameOverride: "full-foo-name-override"`),
)
b := NewLocalBuilder(dm)
- cb, err := b.Build(context.TODO(), tt.reference, targetPath, tt.buildOpts, keyring)
+ cb, err := b.Build(context.TODO(), tt.reference, targetPath, tt.buildOpts)
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
@@ -226,6 +227,10 @@ fullnameOverride: "full-foo-name-override"`),
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Packaged).To(Equal(tt.wantPackaged), "unexpected Build.Packaged value")
g.Expect(cb.Path).ToNot(BeEmpty(), "empty Build.Path")
+ if tt.buildOpts.Keyring != nil {
+ g.Expect(cb.ProvFilePath).ToNot(BeEmpty(), "empty Build.ProvFilePath")
+ g.Expect(cb.VerificationSignature).ToNot(BeNil(), "nil Build.VerificationSignature")
+ }
// Load the resulting chart and verify the values.
resultChart, err := loader.Load(cb.Path)
@@ -262,7 +267,7 @@ func TestLocalBuilder_Build_CachedChart(t *testing.T) {
// Build first time.
targetPath := filepath.Join(tmpDir, "chart1.tgz")
buildOpts := BuildOptions{}
- cb, err := b.Build(context.TODO(), reference, targetPath, buildOpts, keyring)
+ cb, err := b.Build(context.TODO(), reference, targetPath, buildOpts)
g.Expect(err).ToNot(HaveOccurred())
// Set the result as the CachedChart for second build.
@@ -270,17 +275,46 @@ func TestLocalBuilder_Build_CachedChart(t *testing.T) {
targetPath2 := filepath.Join(tmpDir, "chart2.tgz")
defer os.RemoveAll(targetPath2)
- cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts, keyring)
+ cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Path).To(Equal(targetPath))
// Rebuild with build option Force.
buildOpts.Force = true
- cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts, keyring)
+ cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Path).To(Equal(targetPath2))
}
+func TestLocalBuilder_VerifyCachedChartSig(t *testing.T) {
+ g := NewWithT(t)
+
+ reference := LocalReference{Path: "./../testdata/charts/helmchart-0.1.0.tgz"}
+
+ keyring, err := os.ReadFile("./../testdata/charts/pub.gpg")
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(keyring).ToNot(BeEmpty())
+
+ dm := NewDependencyManager()
+ b := NewLocalBuilder(dm)
+
+ tmpDir, err := os.MkdirTemp("", "local-chart-")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer os.RemoveAll(tmpDir)
+
+ buildOpts := BuildOptions{}
+ buildOpts.Keyring = keyring
+
+ buildOpts.CachedChart = "./../testdata/charts/helmchart-0.1.0.tgz"
+ targetPath2 := filepath.Join(tmpDir, "chart2.tgz")
+ defer os.RemoveAll(targetPath2)
+
+ cb, err := b.Build(context.TODO(), reference, targetPath2, buildOpts)
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(cb.ProvFilePath).ToNot(BeEmpty(), "empty Build.ProvFilePath")
+ g.Expect(cb.VerificationSignature).ToNot(BeNil(), "nil Build.VerificationSignature")
+}
+
func Test_mergeFileValues(t *testing.T) {
tests := []struct {
name string
diff --git a/internal/helm/chart/builder_remote.go b/internal/helm/chart/builder_remote.go
index 54e1bed8c..910ad102a 100644
--- a/internal/helm/chart/builder_remote.go
+++ b/internal/helm/chart/builder_remote.go
@@ -27,6 +27,7 @@ import (
helmchart "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
+ "helm.sh/helm/v3/pkg/provenance"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/runtime/transform"
@@ -62,7 +63,7 @@ func NewRemoteBuilder(repository *repository.ChartRepository) Builder {
// After downloading the chart, it is only packaged if required due to BuildOptions
// modifying the chart, otherwise the exact data as retrieved from the repository
// is written to p, after validating it to be a chart.
-func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, opts BuildOptions, keyring []byte) (*Build, error) {
+func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, opts BuildOptions) (*Build, error) {
remoteRef, ok := ref.(RemoteReference)
if !ok {
err := fmt.Errorf("expected remote chart reference")
@@ -106,15 +107,16 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
requiresPackaging := len(opts.GetValuesFiles()) != 0 || opts.VersionMetadata != ""
- verifyProvFile := func(chart, provFile string) error {
- if keyring != nil {
- err := VerifyProvenanceFile(bytes.NewReader(keyring), chart, provFile)
+ verifyProvFile := func(chart, provFile string) (*provenance.Verification, error) {
+ if opts.Keyring != nil {
+ ver, err := verifyChartWithProvFile(bytes.NewReader(opts.Keyring), chart, provFile)
if err != nil {
err = fmt.Errorf("failed to verify helm chart using provenance file %s: %w", provFile, err)
- return &BuildError{Reason: ErrProvenanceVerification, Err: err}
+ return nil, &BuildError{Reason: ErrProvenanceVerification, Err: err}
}
+ return ver, nil
}
- return nil
+ return nil, nil
}
var provFilePath string
@@ -129,13 +131,16 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
// and continue the build
if err = curMeta.Validate(); err == nil {
if result.Name == curMeta.Name && result.Version == curMeta.Version {
- // We can only verify with provenance file if we didn't package the chart ourselves.
+ // We can only verify a cached chart with provenance file if we didn't
+ // package the chart ourselves, and instead stored it as is.
if !requiresPackaging {
provFilePath = provenanceFilePath(opts.CachedChart)
- if err = verifyProvFile(opts.CachedChart, provFilePath); err != nil {
+ if ver, err := verifyProvFile(opts.CachedChart, provFilePath); err != nil {
return nil, err
+ } else {
+ result.ProvFilePath = provFilePath
+ result.VerificationSignature = buildVerificationSig(ver)
}
- result.ProvFilePath = provFilePath
}
result.Path = opts.CachedChart
result.ValuesFiles = opts.GetValuesFiles()
@@ -155,7 +160,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
err = fmt.Errorf("failed to download chart for remote reference: %w", err)
return result, &BuildError{Reason: ErrChartPull, Err: err}
}
- if keyring != nil {
+ if opts.Keyring != nil {
provFilePath = provenanceFilePath(p)
err := b.remote.DownloadProvenanceFile(cv, provFilePath)
if err != nil {
@@ -166,15 +171,17 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
// This is needed, since the verification will work only if the .tgz file is untampered.
// But we write the packaged chart to disk under a different name, so the provenance file
// will not be valid for this _new_ packaged chart.
- chart, err := util.WriteBytesToFile(chartBuf, fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version), false)
+ chart, err := util.WriteToFile(chartBuf, fmt.Sprintf("%s-%s.tgz", cv.Name, cv.Version))
defer os.Remove(chart.Name())
if err != nil {
return nil, err
}
- if err = verifyProvFile(chart.Name(), provFilePath); err != nil {
+ if ver, err := verifyProvFile(chart.Name(), provFilePath); err != nil {
return nil, err
+ } else {
+ result.ProvFilePath = provFilePath
+ result.VerificationSignature = buildVerificationSig(ver)
}
- result.ProvFilePath = provFilePath
}
// Use literal chart copy from remote if no custom values files options are
@@ -250,7 +257,7 @@ func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interf
// validatePackageAndWriteToPath atomically writes the packaged chart from reader
// to out while validating it by loading the chart metadata from the archive.
func validatePackageAndWriteToPath(b []byte, out string) error {
- tmpFile, err := util.WriteBytesToFile(b, out, true)
+ tmpFile, err := util.WriteToTempFile(b, out)
defer os.Remove(tmpFile.Name())
if err != nil {
diff --git a/internal/helm/chart/builder_remote_test.go b/internal/helm/chart/builder_remote_test.go
index b9795b352..9afd521b9 100644
--- a/internal/helm/chart/builder_remote_test.go
+++ b/internal/helm/chart/builder_remote_test.go
@@ -138,16 +138,22 @@ entries:
wantErr: "Invalid Metadata string",
},
{
- name: "with version metadata",
- reference: RemoteReference{Name: "grafana"},
- repository: mockRepo(),
- buildOpts: BuildOptions{VersionMetadata: "foo"},
+ name: "with version metadata",
+ reference: RemoteReference{Name: "grafana"},
+ repository: mockRepo(),
+ buildOpts: BuildOptions{
+ VersionMetadata: "foo",
+ Keyring: keyring,
+ },
wantVersion: "0.1.0+foo",
wantPackaged: true,
},
{
- name: "default values",
- reference: RemoteReference{Name: "grafana"},
+ name: "default values",
+ reference: RemoteReference{Name: "grafana"},
+ buildOpts: BuildOptions{
+ Keyring: keyring,
+ },
repository: mockRepo(),
wantVersion: "0.1.0",
wantValues: chartutil.Values{
@@ -159,6 +165,7 @@ entries:
reference: RemoteReference{Name: "grafana"},
buildOpts: BuildOptions{
ValuesFiles: []string{"a.yaml", "b.yaml", "c.yaml"},
+ Keyring: keyring,
},
repository: mockRepo(),
wantVersion: "0.1.0",
@@ -187,10 +194,7 @@ entries:
b := NewRemoteBuilder(tt.repository)
- if tt.buildOpts.VersionMetadata != "" {
- keyring = nil
- }
- cb, err := b.Build(context.TODO(), tt.reference, targetPath, tt.buildOpts, keyring)
+ cb, err := b.Build(context.TODO(), tt.reference, targetPath, tt.buildOpts)
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
@@ -201,6 +205,8 @@ entries:
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Packaged).To(Equal(tt.wantPackaged), "unexpected Build.Packaged value")
g.Expect(cb.Path).ToNot(BeEmpty(), "empty Build.Path")
+ g.Expect(cb.ProvFilePath).ToNot(BeEmpty(), "empty Build.ProvFilePath")
+ g.Expect(cb.VerificationSignature).ToNot(BeNil(), "nil Build.VerificationSignature")
// Load the resulting chart and verify the values.
resultChart, err := loader.Load(cb.Path)
@@ -273,8 +279,11 @@ entries:
targetPath := filepath.Join(tmpDir, "helmchart-0.1.0.tgz")
defer os.RemoveAll(targetPath)
buildOpts := BuildOptions{}
- cb, err := b.Build(context.TODO(), reference, targetPath, buildOpts, keyring)
+ buildOpts.Keyring = keyring
+ cb, err := b.Build(context.TODO(), reference, targetPath, buildOpts)
g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(cb.ProvFilePath).ToNot(BeEmpty(), "empty Build.ProvFilePath")
+ g.Expect(cb.VerificationSignature).ToNot(BeNil(), "nil Build.VerificationSignature")
// Set the result as the CachedChart for second build.
buildOpts.CachedChart = cb.Path
@@ -282,15 +291,19 @@ entries:
// Rebuild with a new path.
targetPath2 := filepath.Join(tmpDir, "chart2.tgz")
defer os.RemoveAll(targetPath2)
- cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts, keyring)
+ cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Path).To(Equal(targetPath))
+ g.Expect(cb.ProvFilePath).ToNot(BeEmpty(), "empty Build.ProvFilePath")
+ g.Expect(cb.VerificationSignature).ToNot(BeNil(), "nil Build.VerificationSignature")
// Rebuild with build option Force.
buildOpts.Force = true
- cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts, keyring)
+ cb, err = b.Build(context.TODO(), reference, targetPath2, buildOpts)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cb.Path).To(Equal(targetPath2))
+ g.Expect(cb.ProvFilePath).ToNot(BeEmpty(), "empty Build.ProvFilePath")
+ g.Expect(cb.VerificationSignature).ToNot(BeNil(), "nil Build.VerificationSignature")
}
func Test_mergeChartValues(t *testing.T) {
diff --git a/internal/helm/chart/verify.go b/internal/helm/chart/verify.go
index 26e91c31e..9f0870b24 100644
--- a/internal/helm/chart/verify.go
+++ b/internal/helm/chart/verify.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2021 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package chart
import (
@@ -14,14 +30,14 @@ import (
// Ref: https://github.com/helm/helm/blob/v3.8.0/pkg/downloader/chart_downloader.go#L328
// modified to accept a custom provenance file path and an actual keyring instead of a
// path to the file containing the keyring.
-func VerifyProvenanceFile(keyring io.Reader, chartPath, provFilePath string) error {
+func verifyChartWithProvFile(keyring io.Reader, chartPath, provFilePath string) (*provenance.Verification, error) {
switch fi, err := os.Stat(chartPath); {
case err != nil:
- return err
+ return nil, err
case fi.IsDir():
- return fmt.Errorf("unpacked charts cannot be verified")
+ return nil, fmt.Errorf("unpacked charts cannot be verified")
case !isTar(chartPath):
- return fmt.Errorf("chart must be a tgz file")
+ return nil, fmt.Errorf("chart must be a tgz file")
}
if provFilePath == "" {
@@ -29,20 +45,17 @@ func VerifyProvenanceFile(keyring io.Reader, chartPath, provFilePath string) err
}
if _, err := os.Stat(provFilePath); err != nil {
- return fmt.Errorf("could not load provenance file %s: %w", provFilePath, err)
+ return nil, fmt.Errorf("could not load provenance file %s: %w", provFilePath, err)
}
ring, err := openpgp.ReadKeyRing(keyring)
if err != nil {
- return err
+ return nil, err
}
sig := &provenance.Signatory{KeyRing: ring}
- _, err = sig.Verify(chartPath, provFilePath)
- if err != nil {
- return err
- }
- return nil
+ verification, err := sig.Verify(chartPath, provFilePath)
+ return verification, err
}
// isTar tests whether the given file is a tar file.
@@ -55,3 +68,24 @@ func isTar(filename string) bool {
func provenanceFilePath(path string) string {
return path + ".prov"
}
+
+// ref: https://github.com/helm/helm/blob/v3.8.0/pkg/action/verify.go#L47-L51
+type VerificationSignature struct {
+ Identities []string
+ KeyFingerprint [20]byte
+ FileHash string
+}
+
+func buildVerificationSig(ver *provenance.Verification) *VerificationSignature {
+ var verSig VerificationSignature
+ if ver != nil {
+ if ver.SignedBy != nil {
+ for name := range ver.SignedBy.Identities {
+ verSig.Identities = append(verSig.Identities, name)
+ }
+ }
+ verSig.FileHash = ver.FileHash
+ verSig.KeyFingerprint = ver.SignedBy.PrimaryKey.Fingerprint
+ }
+ return &verSig
+}
diff --git a/internal/helm/repository/chart_repository.go b/internal/helm/repository/chart_repository.go
index 3bc1fa63d..3bd76a726 100644
--- a/internal/helm/repository/chart_repository.go
+++ b/internal/helm/repository/chart_repository.go
@@ -216,7 +216,7 @@ func (r *ChartRepository) DownloadProvenanceFile(chart *repo.ChartVersion, path
if err != nil {
return err
}
- tmpFile, err := util.WriteBytesToFile(res.Bytes(), path, true)
+ tmpFile, err := util.WriteToTempFile(res.Bytes(), path)
defer os.Remove(tmpFile.Name())
if err != nil {
diff --git a/internal/util/file.go b/internal/util/file.go
new file mode 100644
index 000000000..dc023d318
--- /dev/null
+++ b/internal/util/file.go
@@ -0,0 +1,43 @@
+package util
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+func writeBytesToFile(bytes []byte, out string, temp bool) (*os.File, error) {
+ var file *os.File
+ var err error
+
+ if temp {
+ file, err = os.CreateTemp("", out)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create temporary file %s: %w", filepath.Base(out), err)
+ }
+ } else {
+ file, err = os.Create(out)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create temporary file for chart %s: %w", out, err)
+ }
+ }
+ if _, err := file.Write(bytes); err != nil {
+ _ = file.Close()
+ return nil, fmt.Errorf("failed to write to file %s: %w", file.Name(), err)
+ }
+ if err := file.Close(); err != nil {
+ return nil, err
+ }
+ return file, nil
+}
+
+// Writes the provided bytes to a file at the given path and returns the file handle.
+func WriteToFile(bytes []byte, path string) (*os.File, error) {
+ return writeBytesToFile(bytes, path, false)
+}
+
+// Writes the provided bytes to a temp file with the name provided in the path and
+// returns the file handle.
+func WriteToTempFile(bytes []byte, out string) (*os.File, error) {
+ return writeBytesToFile(bytes, filepath.Base(out), true)
+}
diff --git a/internal/util/temp.go b/internal/util/temp.go
index ef07c7bd3..054b12801 100644
--- a/internal/util/temp.go
+++ b/internal/util/temp.go
@@ -50,29 +50,3 @@ func pattern(obj client.Object) (p string) {
kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
return fmt.Sprintf("%s-%s-%s-", kind, obj.GetNamespace(), obj.GetName())
}
-
-// TODO: think of a better name?
-func WriteBytesToFile(bytes []byte, out string, temp bool) (*os.File, error) {
- var file *os.File
- var err error
-
- if temp {
- file, err = os.CreateTemp("", filepath.Base(out))
- if err != nil {
- return nil, fmt.Errorf("failed to create temporary file %s: %w", filepath.Base(out), err)
- }
- } else {
- file, err = os.Create(out)
- if err != nil {
- return nil, fmt.Errorf("failed to create temporary file for chart %s: %w", out, err)
- }
- }
- if _, err := file.Write(bytes); err != nil {
- _ = file.Close()
- return nil, fmt.Errorf("failed to write to file %s: %w", file.Name(), err)
- }
- if err := file.Close(); err != nil {
- return nil, err
- }
- return file, nil
-}
From f39ee529baf5ea13938500323c2daead31c869b9 Mon Sep 17 00:00:00 2001
From: Sanskar Jaiswal
Date: Wed, 16 Mar 2022 15:21:42 +0530
Subject: [PATCH 06/10] make doc changes and fix reconilation process
Signed-off-by: Sanskar Jaiswal
---
api/v1beta2/helmchart_types.go | 8 +-
controllers/helmchart_controller.go | 49 +++------
controllers/helmchart_controller_test.go | 131 +++++++++++++----------
internal/helm/chart/builder.go | 6 +-
internal/helm/chart/builder_local.go | 14 ++-
internal/helm/chart/builder_remote.go | 18 ++--
internal/helm/chart/verify_test.go | 18 ++++
internal/util/file.go | 16 +++
8 files changed, 155 insertions(+), 105 deletions(-)
create mode 100644 internal/helm/chart/verify_test.go
diff --git a/api/v1beta2/helmchart_types.go b/api/v1beta2/helmchart_types.go
index 660356535..5435d42ff 100644
--- a/api/v1beta2/helmchart_types.go
+++ b/api/v1beta2/helmchart_types.go
@@ -85,7 +85,7 @@ type HelmChartSpec struct {
// +optional
AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"`
- // Keyring information for verifying the packaged chart's signature using a provenance file.
+ // VerificationKeyring for verifying the packaged chart's signature using a provenance file.
// +optional
VerificationKeyring *VerificationKeyring `json:"verificationKeyring,omitempty"`
}
@@ -94,7 +94,7 @@ type VerificationKeyring struct {
// +required
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
- // The key that corresponds to the keyring value.
+ // Key in the SecretRef that contains the public keyring in legacy GPG format.
// +kubebuilder:default:=pubring.gpg
// +optional
Key string `json:"key,omitempty"`
@@ -168,6 +168,10 @@ const (
// ChartPackageSucceededReason signals that the package of the Helm
// chart succeeded.
ChartPackageSucceededReason string = "ChartPackageSucceeded"
+
+ // ChartVerifiedSucceededReason signals that the Helm chart's signature
+ // has been verified using it's provenance file.
+ ChartVerifiedSucceededReason string = "ChartVerifiedSucceeded"
)
// GetConditions returns the status conditions of the object.
diff --git a/controllers/helmchart_controller.go b/controllers/helmchart_controller.go
index ddd201d3c..aeb23f6e0 100644
--- a/controllers/helmchart_controller.go
+++ b/controllers/helmchart_controller.go
@@ -97,8 +97,6 @@ var helmChartReadyCondition = summarize.Conditions{
},
}
-const KeyringFileName = "pubring.gpg"
-
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/finalizers,verbs=get;create;update;patch;delete
@@ -475,9 +473,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
- Reason: sourcev1.SourceVerifiedCondition,
+ Reason: sourcev1.AuthenticationFailedReason,
}
- conditions.MarkFalse(obj, sourcev1.FetchFailedCondition, sourcev1.SourceVerifiedCondition, e.Error())
+ conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
opts.Keyring = keyring
@@ -609,9 +607,9 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
- Reason: sourcev1.SourceVerifiedCondition,
+ Reason: sourcev1.AuthenticationFailedReason,
}
- conditions.MarkFalse(obj, sourcev1.FetchFailedCondition, sourcev1.SourceVerifiedCondition, e.Error())
+ conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
opts.Keyring = keyring
@@ -651,14 +649,6 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
conditions.MarkTrue(obj, meta.ReadyCondition, reasonForBuild(b), b.Summary())
}
- if b.VerificationSignature != nil && b.ProvFilePath != "" && obj.GetArtifact() != nil {
- var sigVerMsg strings.Builder
- sigVerMsg.WriteString(fmt.Sprintf("chart signed by: %v", strings.Join(b.VerificationSignature.Identities[:], ",")))
- sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: %X", b.VerificationSignature.KeyFingerprint))
- sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: %s", b.VerificationSignature.FileHash))
-
- conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, reasonForBuild(b), sigVerMsg.String())
- }
}()
// Create artifact from build data
@@ -914,22 +904,6 @@ func (r *HelmChartReconciler) getHelmRepositorySecret(ctx context.Context, repos
return &secret, nil
}
-func (r *HelmChartReconciler) getVerificationKeyringSecret(ctx context.Context, chart *sourcev1.HelmChart) (*corev1.Secret, error) {
- if chart.Spec.VerificationKeyring == nil {
- return nil, nil
- }
- name := types.NamespacedName{
- Namespace: chart.GetNamespace(),
- Name: chart.Spec.VerificationKeyring.SecretRef.Name,
- }
- var secret corev1.Secret
- err := r.Client.Get(ctx, name, &secret)
- if err != nil {
- return nil, err
- }
- return &secret, nil
-}
-
func (r *HelmChartReconciler) indexHelmRepositoryByURL(o client.Object) []string {
repo, ok := o.(*sourcev1.HelmRepository)
if !ok {
@@ -1060,6 +1034,15 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
conditions.Delete(obj, sourcev1.BuildFailedCondition)
}
+ if build.VerificationSignature != nil && build.ProvFilePath != "" {
+ var sigVerMsg strings.Builder
+ sigVerMsg.WriteString(fmt.Sprintf("chart signed by: %v", strings.Join(build.VerificationSignature.Identities[:], ",")))
+ sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: %X", build.VerificationSignature.KeyFingerprint))
+ sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: %s", build.VerificationSignature.FileHash))
+
+ conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, sigVerMsg.String())
+ }
+
if err != nil {
var buildErr *chart.BuildError
if ok := errors.As(err, &buildErr); !ok {
@@ -1105,10 +1088,10 @@ func (r *HelmChartReconciler) getProvenanceKeyring(ctx context.Context, chart *s
return nil, err
}
key := chart.Spec.VerificationKeyring.Key
- if val, ok := secret.Data[key]; !ok {
+ val, ok := secret.Data[key]
+ if !ok {
err = fmt.Errorf("secret doesn't contain the advertised verification keyring name %s", key)
return nil, err
- } else {
- return val, nil
}
+ return val, nil
}
diff --git a/controllers/helmchart_controller_test.go b/controllers/helmchart_controller_test.go
index 37b08630f..55e0cdfde 100644
--- a/controllers/helmchart_controller_test.go
+++ b/controllers/helmchart_controller_test.go
@@ -52,7 +52,7 @@ import (
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
)
-const publicKeyFileName = "pub.pgp"
+const publicKeyFileName = "pub.gpg"
func TestHelmChartReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)
@@ -458,14 +458,19 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
}
g.Expect(storage.Archive(gitArtifact, "testdata/charts", nil)).To(Succeed())
+ keyring, err := os.ReadFile("testdata/charts/pub.gpg")
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(keyring).ToNot(BeEmpty())
+
tests := []struct {
- name string
- source sourcev1.Source
- beforeFunc func(obj *sourcev1.HelmChart)
- want sreconcile.Result
- wantErr error
- assertFunc func(g *WithT, build chart.Build, obj sourcev1.HelmChart)
- cleanFunc func(g *WithT, build *chart.Build)
+ name string
+ source sourcev1.Source
+ keyringSecret *corev1.Secret
+ beforeFunc func(obj *sourcev1.HelmChart)
+ want sreconcile.Result
+ wantErr error
+ assertFunc func(g *WithT, build chart.Build, obj sourcev1.HelmChart)
+ cleanFunc func(g *WithT, build *chart.Build)
}{
{
name: "Observes Artifact revision and build result",
@@ -501,6 +506,59 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
g.Expect(os.Remove(build.Path)).To(Succeed())
},
},
+ {
+ name: "Observes Artifact revision and build result with valid signature",
+ source: &sourcev1.GitRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "gitrepository",
+ Namespace: "default",
+ },
+ Status: sourcev1.GitRepositoryStatus{
+ Artifact: gitArtifact,
+ },
+ },
+ keyringSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "keyring-secret",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ publicKeyFileName: keyring,
+ },
+ },
+ beforeFunc: func(obj *sourcev1.HelmChart) {
+ obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
+ obj.Spec.SourceRef = sourcev1.LocalHelmChartSourceReference{
+ Name: "gitrepository",
+ Kind: sourcev1.GitRepositoryKind,
+ }
+ obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
+ SecretRef: meta.LocalObjectReference{
+ Name: "keyring-secret",
+ },
+ Key: publicKeyFileName,
+ }
+ },
+ want: sreconcile.ResultSuccess,
+ assertFunc: func(g *WithT, build chart.Build, obj sourcev1.HelmChart) {
+ g.Expect(build.Complete()).To(BeTrue())
+ g.Expect(build.Name).To(Equal("helmchart"))
+ g.Expect(build.Version).To(Equal("0.1.0"))
+ g.Expect(build.Path).To(BeARegularFile())
+ g.Expect(build.VerificationSignature).ToNot(BeNil())
+ g.Expect(build.ProvFilePath).To(BeARegularFile())
+
+ g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal(gitArtifact.Revision))
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled 'helmchart' chart with version '0.1.0'"),
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, "chart signed by: TestUser using key with fingeprint: 943CB5929ECDA2B5B5EC88BC7035BA97D32A87C1 and hash verified: sha256:007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a"),
+ }))
+ },
+ cleanFunc: func(g *WithT, build *chart.Build) {
+ g.Expect(os.Remove(build.Path)).To(Succeed())
+ g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
+ },
+ },
{
name: "Error on unavailable source",
beforeFunc: func(obj *sourcev1.HelmChart) {
@@ -605,6 +663,9 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
if tt.source != nil {
clientBuilder.WithRuntimeObjects(tt.source)
}
+ if tt.keyringSecret != nil {
+ clientBuilder.WithRuntimeObjects(tt.keyringSecret)
+ }
r := &HelmChartReconciler{
Client: clientBuilder.Build(),
@@ -1129,7 +1190,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
{
name: "Copying artifact to storage from build makes Ready=True",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
beforeFunc: func(obj *sourcev1.HelmChart) {
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
},
@@ -1145,24 +1206,6 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
*conditions.TrueCondition(meta.ReadyCondition, sourcev1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
},
},
- {
- name: "Build with a verified signature sets SourceVerifiedCondition=Truue",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", "testdata/charts/helmchart-0.1.0.tgz.prov"),
- beforeFunc: func(obj *sourcev1.HelmChart) {
- obj.Status.Artifact = &sourcev1.Artifact{
- Path: "testdata/charts/helmchart-0.1.0.tgz",
- }
- },
- want: sreconcile.ResultSuccess,
- afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
- provArtifact := testStorage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "0.1.0", "helmchart-0.1.0.tgz.prov")
- t.Expect(provArtifact.Path).ToNot(BeEmpty())
- },
- assertConditions: []metav1.Condition{
- *conditions.TrueCondition(meta.ReadyCondition, sourcev1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
- *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartPullSucceededReason, "chart signed by: TestUser1,TestUser2 using key with fingeprint: 0102000000000000000000000000000000000000 and hash verified: 53gntj23r24asnf0"),
- },
- },
{
name: "Up-to-date chart build does not persist artifact to storage",
build: &chart.Build{
@@ -1208,7 +1251,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
{
name: "Removes ArtifactOutdatedCondition after creating new artifact",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
beforeFunc: func(obj *sourcev1.HelmChart) {
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
},
@@ -1226,7 +1269,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
},
{
name: "Creates latest symlink to the created artifact",
- build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
+ build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
t.Expect(obj.GetArtifact()).ToNot(BeNil())
@@ -1726,10 +1769,8 @@ func TestHelmChartReconciler_reconcileSubRecs(t *testing.T) {
}
}
-func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
+func mockChartBuild(name, version, path string) *chart.Build {
var copyP string
- var copyPP string
- var verSig *chart.VerificationSignature
if path != "" {
f, err := os.Open(path)
if err == nil {
@@ -1743,29 +1784,9 @@ func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
}
}
}
- if provFilePath != "" {
- f, err := os.Open(provFilePath)
- if err == nil {
- defer f.Close()
- ff, err := os.CreateTemp("", "chart-mock-*.tgz.prov")
- if err == nil {
- defer ff.Close()
- if _, err = io.Copy(ff, f); err == nil {
- copyPP = ff.Name()
- }
- }
- verSig = &chart.VerificationSignature{
- FileHash: "53gntj23r24asnf0",
- Identities: []string{"TestUser1", "TestUser2"},
- KeyFingerprint: [20]byte{1, 2},
- }
- }
- }
return &chart.Build{
- Name: name,
- Version: version,
- Path: copyP,
- ProvFilePath: copyPP,
- VerificationSignature: verSig,
+ Name: name,
+ Version: version,
+ Path: copyP,
}
}
diff --git a/internal/helm/chart/builder.go b/internal/helm/chart/builder.go
index 50fa9ffc5..6250acad3 100644
--- a/internal/helm/chart/builder.go
+++ b/internal/helm/chart/builder.go
@@ -105,8 +105,8 @@ type BuildOptions struct {
// because the list of ValuesFiles has changed.
Force bool
- // Keyring can be set to the data of the chart VerificationKeyring secret
- // used for verifying a chart's signature using a provenance file.
+ // Keyring can be configured with the bytes of a public kering in legacy
+ // PGP format used for verifying a chart's signature using a provenance file.
Keyring []byte
}
@@ -134,7 +134,7 @@ type Build struct {
// chart is not verified.
ProvFilePath string
// VerificationSignature is populated when a chart's signature
- // is susccessfully verified using it's provenance file.
+ // is successfully verified using it's provenance file.
VerificationSignature *VerificationSignature
// ValuesFiles is the list of files used to compose the chart's
// default "values.yaml".
diff --git a/internal/helm/chart/builder_local.go b/internal/helm/chart/builder_local.go
index 6885e1d40..7e5bdf31f 100644
--- a/internal/helm/chart/builder_local.go
+++ b/internal/helm/chart/builder_local.go
@@ -137,9 +137,11 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
// package the chart ourselves, and instead stored it as is.
if !requiresPackaging {
provFilePath = provenanceFilePath(opts.CachedChart)
- if ver, err := verifyProvFile(opts.CachedChart, provFilePath); err != nil {
+ ver, err := verifyProvFile(opts.CachedChart, provFilePath)
+ if err != nil {
return nil, err
- } else {
+ }
+ if ver != nil {
result.VerificationSignature = buildVerificationSig(ver)
result.ProvFilePath = provFilePath
}
@@ -163,9 +165,11 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
if err = copyFileToPath(provenanceFilePath(localRef.Path), provFilePath); err != nil {
return result, &BuildError{Reason: ErrChartPull, Err: err}
}
- if ver, err := verifyProvFile(localRef.Path, provFilePath); err != nil {
- return result, err
- } else {
+ ver, err := verifyProvFile(localRef.Path, provFilePath)
+ if err != nil {
+ return nil, err
+ }
+ if ver != nil {
result.ProvFilePath = provFilePath
result.VerificationSignature = buildVerificationSig(ver)
}
diff --git a/internal/helm/chart/builder_remote.go b/internal/helm/chart/builder_remote.go
index 910ad102a..90c522812 100644
--- a/internal/helm/chart/builder_remote.go
+++ b/internal/helm/chart/builder_remote.go
@@ -135,9 +135,11 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
// package the chart ourselves, and instead stored it as is.
if !requiresPackaging {
provFilePath = provenanceFilePath(opts.CachedChart)
- if ver, err := verifyProvFile(opts.CachedChart, provFilePath); err != nil {
+ ver, err := verifyProvFile(opts.CachedChart, provFilePath)
+ if err != nil {
return nil, err
- } else {
+ }
+ if ver != nil {
result.ProvFilePath = provFilePath
result.VerificationSignature = buildVerificationSig(ver)
}
@@ -153,13 +155,13 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
// Download the package for the resolved version
res, err := b.remote.DownloadChart(cv)
- // Deal with the underlying byte slice to avoid having to read the buffer multiple times.
- chartBuf := res.Bytes()
-
if err != nil {
err = fmt.Errorf("failed to download chart for remote reference: %w", err)
return result, &BuildError{Reason: ErrChartPull, Err: err}
}
+ // Deal with the underlying byte slice to avoid having to read the buffer multiple times.
+ chartBuf := res.Bytes()
+
if opts.Keyring != nil {
provFilePath = provenanceFilePath(p)
err := b.remote.DownloadProvenanceFile(cv, provFilePath)
@@ -176,9 +178,11 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
if err != nil {
return nil, err
}
- if ver, err := verifyProvFile(chart.Name(), provFilePath); err != nil {
+ ver, err := verifyProvFile(chart.Name(), provFilePath)
+ if err != nil {
return nil, err
- } else {
+ }
+ if ver != nil {
result.ProvFilePath = provFilePath
result.VerificationSignature = buildVerificationSig(ver)
}
diff --git a/internal/helm/chart/verify_test.go b/internal/helm/chart/verify_test.go
new file mode 100644
index 000000000..86309714f
--- /dev/null
+++ b/internal/helm/chart/verify_test.go
@@ -0,0 +1,18 @@
+package chart
+
+import (
+ "os"
+ "testing"
+
+ . "github.com/onsi/gomega"
+)
+
+func Test_verifyChartWithProvFile(t *testing.T) {
+ g := NewWithT(t)
+
+ keyring, err := os.Open("../testdata/charts/pub.gpg")
+ g.Expect(err).ToNot(HaveOccurred())
+ ver, err := verifyChartWithProvFile(keyring, "../testdata/charts/helmchart-0.1.0.tgz", "../testdata/charts/helmchart-0.1.0.tgz.prov")
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(ver).ToNot(BeNil())
+}
diff --git a/internal/util/file.go b/internal/util/file.go
index dc023d318..fed8a73f2 100644
--- a/internal/util/file.go
+++ b/internal/util/file.go
@@ -1,3 +1,19 @@
+/*
+Copyright 2022 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package util
import (
From 51a5e77af6cbbb60de67c49a61a72c7b451ee741 Mon Sep 17 00:00:00 2001
From: Sanskar Jaiswal
Date: Thu, 17 Mar 2022 14:51:07 +0530
Subject: [PATCH 07/10] add more docs and fix sig verification error reasons
Signed-off-by: Sanskar Jaiswal
---
api/v1beta2/condition_types.go | 4 +
api/v1beta2/helmchart_types.go | 3 +
api/v1beta2/zz_generated.deepcopy.go | 21 ++++++
.../source.toolkit.fluxcd.io_helmcharts.yaml | 9 ++-
controllers/gitrepository_controller.go | 2 +-
controllers/gitrepository_controller_test.go | 2 +-
controllers/helmchart_controller.go | 29 +++----
controllers/helmchart_controller_test.go | 2 +-
controllers/storage.go | 8 +-
docs/api/source.md | 75 +++++++++++++++++++
internal/helm/chart/builder.go | 6 +-
internal/util/file.go | 34 ++++-----
12 files changed, 150 insertions(+), 45 deletions(-)
diff --git a/api/v1beta2/condition_types.go b/api/v1beta2/condition_types.go
index 647b8aa7f..3b131bfcd 100644
--- a/api/v1beta2/condition_types.go
+++ b/api/v1beta2/condition_types.go
@@ -85,4 +85,8 @@ const (
// SymlinkUpdateFailedReason signals a failure in updating a symlink.
SymlinkUpdateFailedReason string = "SymlinkUpdateFailed"
+
+ // VerificationFailedReason signals a failure in verifying the signature of
+ // an artifact instance, such as a git commit or a helm chart.
+ VerificationFailedReason string = "VerificationFailed"
)
diff --git a/api/v1beta2/helmchart_types.go b/api/v1beta2/helmchart_types.go
index 5435d42ff..afaa5c1b4 100644
--- a/api/v1beta2/helmchart_types.go
+++ b/api/v1beta2/helmchart_types.go
@@ -90,7 +90,10 @@ type HelmChartSpec struct {
VerificationKeyring *VerificationKeyring `json:"verificationKeyring,omitempty"`
}
+// VerificationKeyring contains enough info to get the public GPG key to be used for verifying
+// the chart signature using a provenance file.
type VerificationKeyring struct {
+ // SecretRef is a reference to the secret that contains the public GPG key.
// +required
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index b789d81da..6cfbc3e35 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -442,6 +442,11 @@ func (in *HelmChartSpec) DeepCopyInto(out *HelmChartSpec) {
*out = new(acl.AccessFrom)
(*in).DeepCopyInto(*out)
}
+ if in.VerificationKeyring != nil {
+ in, out := &in.VerificationKeyring, &out.VerificationKeyring
+ *out = new(VerificationKeyring)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartSpec.
@@ -614,3 +619,19 @@ func (in *LocalHelmChartSourceReference) DeepCopy() *LocalHelmChartSourceReferen
in.DeepCopyInto(out)
return out
}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VerificationKeyring) DeepCopyInto(out *VerificationKeyring) {
+ *out = *in
+ out.SecretRef = in.SecretRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VerificationKeyring.
+func (in *VerificationKeyring) DeepCopy() *VerificationKeyring {
+ if in == nil {
+ return nil
+ }
+ out := new(VerificationKeyring)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml b/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml
index 1671ac596..2026e7efa 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml
@@ -405,16 +405,17 @@ spec:
type: string
type: array
verificationKeyring:
- description: Keyring information for verifying the packaged chart's
+ description: VerificationKeyring for verifying the packaged chart's
signature using a provenance file.
properties:
key:
default: pubring.gpg
- description: The key that corresponds to the keyring value.
+ description: Key in the SecretRef that contains the public keyring
+ in legacy GPG format.
type: string
secretRef:
- description: LocalObjectReference contains enough information
- to locate the referenced Kubernetes resource object.
+ description: SecretRef is a reference to the secret that contains
+ the public GPG key.
properties:
name:
description: Name of the referent.
diff --git a/controllers/gitrepository_controller.go b/controllers/gitrepository_controller.go
index 9c9189ca8..d7df0a90a 100644
--- a/controllers/gitrepository_controller.go
+++ b/controllers/gitrepository_controller.go
@@ -638,7 +638,7 @@ func (r *GitRepositoryReconciler) verifyCommitSignature(ctx context.Context, obj
if err := r.Client.Get(ctx, publicKeySecret, secret); err != nil {
e := &serror.Event{
Err: fmt.Errorf("PGP public keys secret error: %w", err),
- Reason: "VerificationError",
+ Reason: sourcev1.VerificationFailedReason,
}
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
diff --git a/controllers/gitrepository_controller_test.go b/controllers/gitrepository_controller_test.go
index 7b6aeba35..e7daac2b7 100644
--- a/controllers/gitrepository_controller_test.go
+++ b/controllers/gitrepository_controller_test.go
@@ -1209,7 +1209,7 @@ func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
},
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "VerificationError", "PGP public keys secret error: secrets \"none-existing\" not found"),
+ *conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationFailedReason, "PGP public keys secret error: secrets \"none-existing\" not found"),
},
},
{
diff --git a/controllers/helmchart_controller.go b/controllers/helmchart_controller.go
index aeb23f6e0..2b12210c1 100644
--- a/controllers/helmchart_controller.go
+++ b/controllers/helmchart_controller.go
@@ -473,9 +473,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
- Reason: sourcev1.AuthenticationFailedReason,
+ Reason: sourcev1.VerificationFailedReason,
}
- conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
+ conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
opts.Keyring = keyring
@@ -483,7 +483,6 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
// Build the chart
ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version}
build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts)
-
if err != nil {
return sreconcile.ResultEmpty, err
}
@@ -607,9 +606,9 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
- Reason: sourcev1.AuthenticationFailedReason,
+ Reason: sourcev1.VerificationFailedReason,
}
- conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
+ conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
opts.Keyring = keyring
@@ -698,10 +697,11 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
if b.ProvFilePath != "" {
provArtifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), b.Version, fmt.Sprintf("%s-%s.tgz.prov", b.Name, b.Version))
if err = r.Storage.CopyFromPath(&provArtifact, b.ProvFilePath); err != nil {
- return sreconcile.ResultEmpty, &serror.Event{
+ e := &serror.Event{
Err: fmt.Errorf("unable to copy Helm chart provenance file to storage: %w", err),
- Reason: sourcev1.StorageOperationFailedCondition,
+ Reason: sourcev1.ArchiveOperationFailedReason,
}
+ conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
}
}
@@ -803,14 +803,13 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
localPath := r.Storage.LocalPath(*obj.GetArtifact())
provFilePath := localPath + ".prov"
dir := filepath.Dir(localPath)
- callbacks := make([]func(path string, info os.FileInfo) bool, 0)
- callbacks = append(callbacks, func(path string, info os.FileInfo) bool {
+ callback := func(path string, info os.FileInfo) bool {
if path != localPath && path != provFilePath && info.Mode()&os.ModeSymlink != os.ModeSymlink {
return true
}
return false
- })
- if _, err := r.Storage.RemoveConditionally(dir, callbacks); err != nil {
+ }
+ if _, err := r.Storage.RemoveConditionally(dir, callback); err != nil {
return &serror.Event{
Err: fmt.Errorf("garbage collection of old artifacts failed: %w", err),
Reason: "GarbageCollectionFailed",
@@ -1036,11 +1035,13 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
if build.VerificationSignature != nil && build.ProvFilePath != "" {
var sigVerMsg strings.Builder
- sigVerMsg.WriteString(fmt.Sprintf("chart signed by: %v", strings.Join(build.VerificationSignature.Identities[:], ",")))
- sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: %X", build.VerificationSignature.KeyFingerprint))
- sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: %s", build.VerificationSignature.FileHash))
+ sigVerMsg.WriteString(fmt.Sprintf("chart signed by: '%v'", strings.Join(build.VerificationSignature.Identities[:], ",")))
+ sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: '%X'", build.VerificationSignature.KeyFingerprint))
+ sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: '%s'", build.VerificationSignature.FileHash))
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, sigVerMsg.String())
+ } else {
+ conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
}
if err != nil {
diff --git a/controllers/helmchart_controller_test.go b/controllers/helmchart_controller_test.go
index 55e0cdfde..51a701431 100644
--- a/controllers/helmchart_controller_test.go
+++ b/controllers/helmchart_controller_test.go
@@ -551,7 +551,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal(gitArtifact.Revision))
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled 'helmchart' chart with version '0.1.0'"),
- *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, "chart signed by: TestUser using key with fingeprint: 943CB5929ECDA2B5B5EC88BC7035BA97D32A87C1 and hash verified: sha256:007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a"),
+ *conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, "chart signed by: 'TestUser' using key with fingeprint: '943CB5929ECDA2B5B5EC88BC7035BA97D32A87C1' and hash verified: 'sha256:007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a'"),
}))
},
cleanFunc: func(g *WithT, build *chart.Build) {
diff --git a/controllers/storage.go b/controllers/storage.go
index bd39b66b4..54bf06409 100644
--- a/controllers/storage.go
+++ b/controllers/storage.go
@@ -53,6 +53,10 @@ type Storage struct {
Timeout time.Duration `json:"timeout"`
}
+// removeFileCallback is a function which determines whether the
+// provided file should be removed from the filesystem.
+type removeFileCallback func(path string, info os.FileInfo) bool
+
// NewStorage creates the storage helper for a given path and hostname.
func NewStorage(basePath string, hostname string, timeout time.Duration) (*Storage, error) {
if f, err := os.Stat(basePath); os.IsNotExist(err) || !f.IsDir() {
@@ -145,7 +149,9 @@ func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, err
return deletedFiles, nil
}
-func (s *Storage) RemoveConditionally(dir string, callbacks []func(path string, info os.FileInfo) bool) ([]string, error) {
+// RemoveConditionally walks through the provided dir and then deletes all files
+// for which any of the callbacks return true.
+func (s *Storage) RemoveConditionally(dir string, callbacks ...removeFileCallback) ([]string, error) {
deletedFiles := []string{}
var errors []string
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
diff --git a/docs/api/source.md b/docs/api/source.md
index 6f0d1621b..600aa4125 100644
--- a/docs/api/source.md
+++ b/docs/api/source.md
@@ -668,6 +668,20 @@ references to this object.
NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092
|
+
+
+verificationKeyring
+
+
+VerificationKeyring
+
+
+ |
+
+(Optional)
+ VerificationKeyring for verifying the packaged chart’s signature using a provenance file.
+ |
+
@@ -1850,6 +1864,20 @@ references to this object.
NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092