Skip to content

Commit 543b3a9

Browse files
authored
Merge pull request #545 from souleb/enable-cosign-verif
Enable Cosign verification of Helm charts stored as OCI artifacts in container registries
2 parents e543544 + e5f7b8c commit 543b3a9

13 files changed

+262
-12
lines changed

.github/workflows/e2e.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ jobs:
110110
kubectl -n helm-system apply -f config/testdata/podinfo
111111
kubectl -n helm-system wait helmreleases/podinfo --for=condition=ready --timeout=4m
112112
kubectl -n helm-system wait helmreleases/podinfo-git --for=condition=ready --timeout=4m
113+
kubectl -n helm-system wait helmreleases/podinfo-oci --for=condition=ready --timeout=4m
113114
kubectl -n helm-system delete -f config/testdata/podinfo
114115
- name: Run dependency tests
115116
run: |

api/v2beta1/helmrelease_types.go

+21
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,14 @@ type HelmChartTemplateSpec struct {
286286
// +optional
287287
// +deprecated
288288
ValuesFile string `json:"valuesFile,omitempty"`
289+
290+
// Verify contains the secret name containing the trusted public keys
291+
// used to verify the signature and specifies which provider to use to check
292+
// whether OCI image is authentic.
293+
// This field is only supported for OCI sources.
294+
// Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.
295+
// +optional
296+
Verify *HelmChartTemplateVerification `json:"verify,omitempty"`
289297
}
290298

291299
// GetInterval returns the configured interval for the v1beta2.HelmChart,
@@ -306,6 +314,19 @@ func (in HelmChartTemplate) GetNamespace(defaultNamespace string) string {
306314
return in.Spec.SourceRef.Namespace
307315
}
308316

317+
// HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.
318+
type HelmChartTemplateVerification struct {
319+
// Provider specifies the technology used to sign the OCI Helm chart.
320+
// +kubebuilder:validation:Enum=cosign
321+
// +kubebuilder:default:=cosign
322+
Provider string `json:"provider"`
323+
324+
// SecretRef specifies the Kubernetes Secret containing the
325+
// trusted public keys.
326+
// +optional
327+
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
328+
}
329+
309330
// DeploymentAction defines a consistent interface for Install and Upgrade.
310331
// +kubebuilder:object:generate=false
311332
type DeploymentAction interface {

api/v2beta1/zz_generated.deepcopy.go

+25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml

+28
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,34 @@ spec:
117117
items:
118118
type: string
119119
type: array
120+
verify:
121+
description: Verify contains the secret name containing the
122+
trusted public keys used to verify the signature and specifies
123+
which provider to use to check whether OCI image is authentic.
124+
This field is only supported for OCI sources. Chart dependencies,
125+
which are not bundled in the umbrella chart artifact, are
126+
not verified.
127+
properties:
128+
provider:
129+
default: cosign
130+
description: Provider specifies the technology used to
131+
sign the OCI Helm chart.
132+
enum:
133+
- cosign
134+
type: string
135+
secretRef:
136+
description: SecretRef specifies the Kubernetes Secret
137+
containing the trusted public keys.
138+
properties:
139+
name:
140+
description: Name of the referent.
141+
type: string
142+
required:
143+
- name
144+
type: object
145+
required:
146+
- provider
147+
type: object
120148
version:
121149
default: '*'
122150
description: Version semver expression, ignored for charts

config/default/kustomization.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
namespace: helm-system
44
resources:
5-
- https://github.com/fluxcd/source-controller/releases/download/v0.25.3/source-controller.crds.yaml
6-
- https://github.com/fluxcd/source-controller/releases/download/v0.25.3/source-controller.deployment.yaml
5+
- https://github.com/fluxcd/source-controller/releases/download/v0.31.0/source-controller.crds.yaml
6+
- https://github.com/fluxcd/source-controller/releases/download/v0.31.0/source-controller.deployment.yaml
77
- ../crd
88
- ../rbac
99
- ../manager
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: helm.toolkit.fluxcd.io/v2beta1
2+
kind: HelmRelease
3+
metadata:
4+
name: podinfo-oci
5+
spec:
6+
interval: 5m
7+
chart:
8+
spec:
9+
chart: podinfo
10+
version: '6.2.1'
11+
sourceRef:
12+
kind: HelmRepository
13+
name: podinfo-oci
14+
interval: 1m
15+
verify:
16+
provider: cosign
17+
values:
18+
resources:
19+
requests:
20+
cpu: 100m
21+
memory: 64Mi

config/testdata/sources/helmrepository.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
apiVersion: source.toolkit.fluxcd.io/v1beta1
1+
apiVersion: source.toolkit.fluxcd.io/v1beta2
22
kind: HelmRepository
33
metadata:
44
name: podinfo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: source.toolkit.fluxcd.io/v1beta2
2+
kind: HelmRepository
3+
metadata:
4+
name: podinfo-oci
5+
spec:
6+
interval: 1m
7+
url: oci://ghcr.io/stefanprodan/charts
8+
type: "oci"

controllers/helmrelease_controller_chart.go

+15
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ func buildHelmChartFromTemplate(hr *v2.HelmRelease) *sourcev1.HelmChart {
211211
ReconcileStrategy: template.Spec.ReconcileStrategy,
212212
ValuesFiles: template.Spec.ValuesFiles,
213213
ValuesFile: template.Spec.ValuesFile,
214+
Verify: templateVerificationToSourceVerification(template.Spec.Verify),
214215
},
215216
}
216217
}
@@ -239,7 +240,21 @@ func helmChartRequiresUpdate(hr *v2.HelmRelease, chart *sourcev1.HelmChart) bool
239240
return true
240241
case template.Spec.ValuesFile != chart.Spec.ValuesFile:
241242
return true
243+
case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify):
244+
return true
242245
default:
243246
return false
244247
}
245248
}
249+
250+
// templateVerificationToSourceVerification converts the HelmChartTemplateVerification to the OCIRepositoryVerification.
251+
func templateVerificationToSourceVerification(template *v2.HelmChartTemplateVerification) *sourcev1.OCIRepositoryVerification {
252+
if template == nil {
253+
return nil
254+
}
255+
256+
return &sourcev1.OCIRepositoryVerification{
257+
Provider: template.Provider,
258+
SecretRef: template.SecretRef,
259+
}
260+
}

controllers/helmrelease_controller_chart_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"testing"
2223
"time"
2324

25+
"github.com/fluxcd/pkg/apis/meta"
2426
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
2527
"github.com/go-logr/logr"
2628
. "github.com/onsi/gomega"
@@ -371,6 +373,39 @@ func Test_buildHelmChartFromTemplate(t *testing.T) {
371373
},
372374
},
373375
},
376+
{
377+
name: "take cosign verification into account",
378+
modify: func(hr *v2.HelmRelease) {
379+
hr.Spec.Chart.Spec.Verify = &v2.HelmChartTemplateVerification{
380+
Provider: "cosign",
381+
SecretRef: &meta.LocalObjectReference{
382+
Name: "cosign-key",
383+
},
384+
}
385+
},
386+
want: &sourcev1.HelmChart{
387+
ObjectMeta: metav1.ObjectMeta{
388+
Name: "default-test-release",
389+
Namespace: "default",
390+
},
391+
Spec: sourcev1.HelmChartSpec{
392+
Chart: "chart",
393+
Version: "1.0.0",
394+
SourceRef: sourcev1.LocalHelmChartSourceReference{
395+
Name: "test-repository",
396+
Kind: "HelmRepository",
397+
},
398+
Interval: metav1.Duration{Duration: 2 * time.Minute},
399+
ValuesFiles: []string{"values.yaml"},
400+
Verify: &sourcev1.OCIRepositoryVerification{
401+
Provider: "cosign",
402+
SecretRef: &meta.LocalObjectReference{
403+
Name: "cosign-key",
404+
},
405+
},
406+
},
407+
},
408+
},
374409
}
375410
for _, tt := range tests {
376411
t.Run(tt.name, func(t *testing.T) {
@@ -398,6 +433,9 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
398433
Kind: "HelmRepository",
399434
},
400435
Interval: &metav1.Duration{Duration: 2 * time.Minute},
436+
Verify: &v2.HelmChartTemplateVerification{
437+
Provider: "cosign",
438+
},
401439
},
402440
},
403441
},
@@ -469,16 +507,26 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
469507
},
470508
want: true,
471509
},
510+
{
511+
name: "detects verify change",
512+
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
513+
hr.Spec.Chart.Spec.Verify.Provider = "foo-bar"
514+
},
515+
want: true,
516+
},
472517
}
473518
for _, tt := range tests {
474519
t.Run(tt.name, func(t *testing.T) {
475520
g := NewWithT(t)
476521

477522
hr := hrWithChartTemplate.DeepCopy()
478523
hc := buildHelmChartFromTemplate(hr)
524+
// second copy to avoid modifying the original
525+
hr = hrWithChartTemplate.DeepCopy()
479526
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(false))
480527

481528
tt.modify(hr, hc)
529+
fmt.Println("verify", hr.Spec.Chart.Spec.Verify.Provider, hc.Spec.Verify.Provider)
482530
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(tt.want))
483531
})
484532
}

docs/api/helmrelease.md

+83
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,24 @@ for backwards compatibility the file defined here is merged before the
566566
ValuesFiles items. Ignored when omitted.</p>
567567
</td>
568568
</tr>
569+
<tr>
570+
<td>
571+
<code>verify</code><br>
572+
<em>
573+
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateVerification">
574+
HelmChartTemplateVerification
575+
</a>
576+
</em>
577+
</td>
578+
<td>
579+
<em>(Optional)</em>
580+
<p>Verify contains the secret name containing the trusted public keys
581+
used to verify the signature and specifies which provider to use to check
582+
whether OCI image is authentic.
583+
This field is only supported for OCI sources.
584+
Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p>
585+
</td>
586+
</tr>
569587
</table>
570588
</td>
571589
</tr>
@@ -688,6 +706,71 @@ for backwards compatibility the file defined here is merged before the
688706
ValuesFiles items. Ignored when omitted.</p>
689707
</td>
690708
</tr>
709+
<tr>
710+
<td>
711+
<code>verify</code><br>
712+
<em>
713+
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateVerification">
714+
HelmChartTemplateVerification
715+
</a>
716+
</em>
717+
</td>
718+
<td>
719+
<em>(Optional)</em>
720+
<p>Verify contains the secret name containing the trusted public keys
721+
used to verify the signature and specifies which provider to use to check
722+
whether OCI image is authentic.
723+
This field is only supported for OCI sources.
724+
Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p>
725+
</td>
726+
</tr>
727+
</tbody>
728+
</table>
729+
</div>
730+
</div>
731+
<h3 id="helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateVerification">HelmChartTemplateVerification
732+
</h3>
733+
<p>
734+
(<em>Appears on:</em>
735+
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateSpec">HelmChartTemplateSpec</a>)
736+
</p>
737+
<p>HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.</p>
738+
<div class="md-typeset__scrollwrap">
739+
<div class="md-typeset__table">
740+
<table>
741+
<thead>
742+
<tr>
743+
<th>Field</th>
744+
<th>Description</th>
745+
</tr>
746+
</thead>
747+
<tbody>
748+
<tr>
749+
<td>
750+
<code>provider</code><br>
751+
<em>
752+
string
753+
</em>
754+
</td>
755+
<td>
756+
<p>Provider specifies the technology used to sign the OCI Helm chart.</p>
757+
</td>
758+
</tr>
759+
<tr>
760+
<td>
761+
<code>secretRef</code><br>
762+
<em>
763+
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
764+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
765+
</a>
766+
</em>
767+
</td>
768+
<td>
769+
<em>(Optional)</em>
770+
<p>SecretRef specifies the Kubernetes Secret containing the
771+
trusted public keys.</p>
772+
</td>
773+
</tr>
691774
</tbody>
692775
</table>
693776
</div>

0 commit comments

Comments
 (0)