Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Cosign verification of Helm charts stored as OCI artifacts in container registries #545

Merged
merged 2 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ jobs:
kubectl -n helm-system apply -f config/testdata/podinfo
kubectl -n helm-system wait helmreleases/podinfo --for=condition=ready --timeout=4m
kubectl -n helm-system wait helmreleases/podinfo-git --for=condition=ready --timeout=4m
kubectl -n helm-system wait helmreleases/podinfo-oci --for=condition=ready --timeout=4m
kubectl -n helm-system delete -f config/testdata/podinfo
- name: Run dependency tests
run: |
Expand Down
21 changes: 21 additions & 0 deletions api/v2beta1/helmrelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ type HelmChartTemplateSpec struct {
// +optional
// +deprecated
ValuesFile string `json:"valuesFile,omitempty"`

// Verify contains the secret name containing the trusted public keys
// used to verify the signature and specifies which provider to use to check
// whether OCI image is authentic.
// This field is only supported for OCI sources.
// Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.
// +optional
Verify *HelmChartTemplateVerification `json:"verify,omitempty"`
}

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

// HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.
type HelmChartTemplateVerification struct {
// Provider specifies the technology used to sign the OCI Helm chart.
// +kubebuilder:validation:Enum=cosign
// +kubebuilder:default:=cosign
Provider string `json:"provider"`

// SecretRef specifies the Kubernetes Secret containing the
// trusted public keys.
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
}

// DeploymentAction defines a consistent interface for Install and Upgrade.
// +kubebuilder:object:generate=false
type DeploymentAction interface {
Expand Down
25 changes: 25 additions & 0 deletions api/v2beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,34 @@ spec:
items:
type: string
type: array
verify:
description: Verify contains the secret name containing the
trusted public keys used to verify the signature and specifies
which provider to use to check whether OCI image is authentic.
This field is only supported for OCI sources. Chart dependencies,
which are not bundled in the umbrella chart artifact, are
not verified.
properties:
provider:
default: cosign
description: Provider specifies the technology used to
sign the OCI Helm chart.
enum:
- cosign
type: string
secretRef:
description: SecretRef specifies the Kubernetes Secret
containing the trusted public keys.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
required:
- provider
type: object
version:
default: '*'
description: Version semver expression, ignored for charts
Expand Down
4 changes: 2 additions & 2 deletions config/default/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: helm-system
resources:
- https://github.com/fluxcd/source-controller/releases/download/v0.25.3/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v0.25.3/source-controller.deployment.yaml
- https://github.com/fluxcd/source-controller/releases/download/v0.31.0/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v0.31.0/source-controller.deployment.yaml
- ../crd
- ../rbac
- ../manager
Expand Down
21 changes: 21 additions & 0 deletions config/testdata/podinfo/helmrelease-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo-oci
spec:
interval: 5m
chart:
spec:
chart: podinfo
version: '6.2.1'
sourceRef:
kind: HelmRepository
name: podinfo-oci
interval: 1m
verify:
provider: cosign
values:
resources:
requests:
cpu: 100m
memory: 64Mi
2 changes: 1 addition & 1 deletion config/testdata/sources/helmrepository.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: source.toolkit.fluxcd.io/v1beta1
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: podinfo
Expand Down
8 changes: 8 additions & 0 deletions config/testdata/sources/helmrepository_oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: podinfo-oci
spec:
interval: 1m
url: oci://ghcr.io/stefanprodan/charts
type: "oci"
15 changes: 15 additions & 0 deletions controllers/helmrelease_controller_chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func buildHelmChartFromTemplate(hr *v2.HelmRelease) *sourcev1.HelmChart {
ReconcileStrategy: template.Spec.ReconcileStrategy,
ValuesFiles: template.Spec.ValuesFiles,
ValuesFile: template.Spec.ValuesFile,
Verify: templateVerificationToSourceVerification(template.Spec.Verify),
},
}
}
Expand Down Expand Up @@ -239,7 +240,21 @@ func helmChartRequiresUpdate(hr *v2.HelmRelease, chart *sourcev1.HelmChart) bool
return true
case template.Spec.ValuesFile != chart.Spec.ValuesFile:
return true
case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify):
return true
default:
return false
}
}

// templateVerificationToSourceVerification converts the HelmChartTemplateVerification to the OCIRepositoryVerification.
func templateVerificationToSourceVerification(template *v2.HelmChartTemplateVerification) *sourcev1.OCIRepositoryVerification {
if template == nil {
return nil
}

return &sourcev1.OCIRepositoryVerification{
Provider: template.Provider,
SecretRef: template.SecretRef,
}
}
48 changes: 48 additions & 0 deletions controllers/helmrelease_controller_chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package controllers

import (
"context"
"fmt"
"testing"
"time"

"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/go-logr/logr"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -371,6 +373,39 @@ func Test_buildHelmChartFromTemplate(t *testing.T) {
},
},
},
{
name: "take cosign verification into account",
modify: func(hr *v2.HelmRelease) {
hr.Spec.Chart.Spec.Verify = &v2.HelmChartTemplateVerification{
Provider: "cosign",
SecretRef: &meta.LocalObjectReference{
Name: "cosign-key",
},
}
},
want: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "default",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: metav1.Duration{Duration: 2 * time.Minute},
ValuesFiles: []string{"values.yaml"},
Verify: &sourcev1.OCIRepositoryVerification{
Provider: "cosign",
SecretRef: &meta.LocalObjectReference{
Name: "cosign-key",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -398,6 +433,9 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
Kind: "HelmRepository",
},
Interval: &metav1.Duration{Duration: 2 * time.Minute},
Verify: &v2.HelmChartTemplateVerification{
Provider: "cosign",
},
},
},
},
Expand Down Expand Up @@ -469,16 +507,26 @@ func Test_helmChartRequiresUpdate(t *testing.T) {
},
want: true,
},
{
name: "detects verify change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.Verify.Provider = "foo-bar"
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

hr := hrWithChartTemplate.DeepCopy()
hc := buildHelmChartFromTemplate(hr)
// second copy to avoid modifying the original
hr = hrWithChartTemplate.DeepCopy()
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(false))

tt.modify(hr, hc)
fmt.Println("verify", hr.Spec.Chart.Spec.Verify.Provider, hc.Spec.Verify.Provider)
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(tt.want))
})
}
Expand Down
83 changes: 83 additions & 0 deletions docs/api/helmrelease.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,24 @@ for backwards compatibility the file defined here is merged before the
ValuesFiles items. Ignored when omitted.</p>
</td>
</tr>
<tr>
<td>
<code>verify</code><br>
<em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateVerification">
HelmChartTemplateVerification
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Verify contains the secret name containing the trusted public keys
used to verify the signature and specifies which provider to use to check
whether OCI image is authentic.
This field is only supported for OCI sources.
Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p>
</td>
</tr>
</table>
</td>
</tr>
Expand Down Expand Up @@ -688,6 +706,71 @@ for backwards compatibility the file defined here is merged before the
ValuesFiles items. Ignored when omitted.</p>
</td>
</tr>
<tr>
<td>
<code>verify</code><br>
<em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateVerification">
HelmChartTemplateVerification
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Verify contains the secret name containing the trusted public keys
used to verify the signature and specifies which provider to use to check
whether OCI image is authentic.
This field is only supported for OCI sources.
Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateVerification">HelmChartTemplateVerification
</h3>
<p>
(<em>Appears on:</em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateSpec">HelmChartTemplateSpec</a>)
</p>
<p>HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>provider</code><br>
<em>
string
</em>
</td>
<td>
<p>Provider specifies the technology used to sign the OCI Helm chart.</p>
</td>
</tr>
<tr>
<td>
<code>secretRef</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>SecretRef specifies the Kubernetes Secret containing the
trusted public keys.</p>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down
Loading