Skip to content

Commit 6bfcb1a

Browse files
committed
update helmchart reconciliation to verify charts using prov file
Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent a03af3b commit 6bfcb1a

File tree

9 files changed

+229
-94
lines changed

9 files changed

+229
-94
lines changed

api/v1beta2/helmchart_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ type HelmChartSpec struct {
7979
// AccessFrom defines an Access Control List for allowing cross-namespace references to this object.
8080
// +optional
8181
AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"`
82+
83+
// Keyring information for verifying the packaged chart's signature using a provenance file.
84+
// +optional
85+
VerificationKeyring *VerificationKeyring `json:"verificationKeyring,omitempty"`
86+
}
87+
88+
type VerificationKeyring struct {
89+
// +required
90+
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
91+
92+
// The key that corresponds to the keyring value.
93+
// +kubebuilder:default:=pubring.gpg
94+
// +optional
95+
Key string `json:"key,omitempty"`
8296
}
8397

8498
const (

config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,25 @@ spec:
400400
items:
401401
type: string
402402
type: array
403+
verificationKeyring:
404+
description: Keyring information for verifying the packaged chart's
405+
signature using a provenance file.
406+
properties:
407+
key:
408+
default: pubring.gpg
409+
description: The key that corresponds to the keyring value.
410+
type: string
411+
secretRef:
412+
description: LocalObjectReference contains enough information
413+
to locate the referenced Kubernetes resource object.
414+
properties:
415+
name:
416+
description: Name of the referent.
417+
type: string
418+
required:
419+
- name
420+
type: object
421+
type: object
403422
version:
404423
default: '*'
405424
description: The chart version semver expression, ignored for charts

controllers/helmchart_controller.go

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ var helmChartReadyConditions = summarize.Conditions{
9191
},
9292
}
9393

94+
const KeyringFileName = "pubring.gpg"
95+
9496
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts,verbs=get;list;watch;create;update;patch;delete
9597
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/status,verbs=get;update;patch
9698
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/finalizers,verbs=get;create;update;patch;delete
@@ -451,13 +453,20 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
451453
opts.VersionMetadata = strconv.FormatInt(obj.Generation, 10)
452454
}
453455

456+
var keyring []byte
457+
keyring, err = r.getProvenanceKeyring(ctx, obj)
458+
if err != nil {
459+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, err.Error())
460+
return sreconcile.ResultEmpty, err
461+
}
462+
454463
// Build the chart
455464
ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version}
456-
build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts)
465+
build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts, keyring)
466+
457467
if err != nil {
458468
return sreconcile.ResultEmpty, err
459469
}
460-
461470
*b = *build
462471
return sreconcile.ResultSuccess, nil
463472
}
@@ -576,13 +585,19 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
576585
}
577586
opts.VersionMetadata += strconv.FormatInt(obj.Generation, 10)
578587
}
588+
var keyring []byte
589+
keyring, err = r.getProvenanceKeyring(ctx, obj)
590+
if err != nil {
591+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, err.Error())
592+
return sreconcile.ResultEmpty, err
593+
}
579594

580595
// Build chart
581596
cb := chart.NewLocalBuilder(dm)
582597
build, err := cb.Build(ctx, chart.LocalReference{
583598
WorkDir: sourceDir,
584599
Path: chartPath,
585-
}, util.TempPathForObj("", ".tgz", obj), opts)
600+
}, util.TempPathForObj("", ".tgz", obj), opts, keyring)
586601
if err != nil {
587602
return sreconcile.ResultEmpty, err
588603
}
@@ -643,6 +658,18 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
643658
}
644659
}
645660

661+
// the provenance file artifact is not recorded, but it shadows the HelmChart artifact
662+
// under the assumption that the file is always available at "chart.tgz.prov"
663+
if b.ProvFilePath != "" {
664+
provArtifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), b.Version, fmt.Sprintf("%s-%s.tgz.prov", b.Name, b.Version))
665+
if err = r.Storage.CopyFromPath(&provArtifact, b.ProvFilePath); err != nil {
666+
return sreconcile.ResultEmpty, &serror.Event{
667+
Err: fmt.Errorf("unable to copy Helm chart provenance file to storage: %w", err),
668+
Reason: sourcev1.StorageOperationFailedReason,
669+
}
670+
}
671+
}
672+
646673
// Record it on the object
647674
obj.Status.Artifact = artifact.DeepCopy()
648675
obj.Status.ObservedChartName = b.Name
@@ -824,6 +851,22 @@ func (r *HelmChartReconciler) getHelmRepositorySecret(ctx context.Context, repos
824851
return &secret, nil
825852
}
826853

854+
func (r *HelmChartReconciler) getVerificationKeyringSecret(ctx context.Context, chart *sourcev1.HelmChart) (*corev1.Secret, error) {
855+
if chart.Spec.VerificationKeyring == nil {
856+
return nil, nil
857+
}
858+
name := types.NamespacedName{
859+
Namespace: chart.GetNamespace(),
860+
Name: chart.Spec.VerificationKeyring.SecretRef.Name,
861+
}
862+
var secret corev1.Secret
863+
err := r.Client.Get(ctx, name, &secret)
864+
if err != nil {
865+
return nil, err
866+
}
867+
return &secret, nil
868+
}
869+
827870
func (r *HelmChartReconciler) indexHelmRepositoryByURL(o client.Object) []string {
828871
repo, ok := o.(*sourcev1.HelmRepository)
829872
if !ok {
@@ -982,3 +1025,33 @@ func reasonForBuild(build *chart.Build) string {
9821025
}
9831026
return sourcev1.ChartPullSucceededReason
9841027
}
1028+
1029+
func (r *HelmChartReconciler) getProvenanceKeyring(ctx context.Context, chart *sourcev1.HelmChart) ([]byte, error) {
1030+
if chart.Spec.VerificationKeyring == nil {
1031+
return nil, nil
1032+
}
1033+
name := types.NamespacedName{
1034+
Namespace: chart.GetNamespace(),
1035+
Name: chart.Spec.VerificationKeyring.SecretRef.Name,
1036+
}
1037+
var secret corev1.Secret
1038+
err := r.Client.Get(ctx, name, &secret)
1039+
if err != nil {
1040+
e := &serror.Event{
1041+
Err: fmt.Errorf("failed to get secret '%s': %w", chart.Spec.VerificationKeyring.SecretRef.Name, err),
1042+
Reason: sourcev1.AuthenticationFailedReason,
1043+
}
1044+
return nil, e
1045+
}
1046+
key := chart.Spec.VerificationKeyring.Key
1047+
if val, ok := secret.Data[key]; !ok {
1048+
err = fmt.Errorf("secret doesn't contain the advertised verification keyring name %s", key)
1049+
e := &serror.Event{
1050+
Err: fmt.Errorf("invalid secret '%s': %w", secret.GetName(), err),
1051+
Reason: sourcev1.AuthenticationFailedReason,
1052+
}
1053+
return nil, e
1054+
} else {
1055+
return val, nil
1056+
}
1057+
}

controllers/storage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func (s *Storage) RemoveAll(artifact sourcev1.Artifact) (string, error) {
120120
func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, error) {
121121
deletedFiles := []string{}
122122
localPath := s.LocalPath(artifact)
123+
localProvPath := localPath + ".prov"
123124
dir := filepath.Dir(localPath)
124125
var errors []string
125126
_ = 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
128129
return nil
129130
}
130131

131-
if path != localPath && !info.IsDir() && info.Mode()&os.ModeSymlink != os.ModeSymlink {
132+
if path != localPath && path != localProvPath && !info.IsDir() && info.Mode()&os.ModeSymlink != os.ModeSymlink {
132133
if err := os.Remove(path); err != nil {
133134
errors = append(errors, info.Name())
134135
} else {

go.mod

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ require (
88
cloud.google.com/go/storage v1.16.0
99
github.com/Masterminds/semver/v3 v3.1.1
1010
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f
11-
github.com/cyphar/filepath-securejoin v0.2.2
11+
github.com/cyphar/filepath-securejoin v0.2.3
1212
github.com/darkowlzz/controller-check v0.0.0-20220119215126-648356cef22c
1313
github.com/docker/go-units v0.4.0
1414
github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b
1515
github.com/fluxcd/pkg/apis/meta v0.12.0
1616
github.com/fluxcd/pkg/gittestserver v0.5.0
1717
github.com/fluxcd/pkg/gitutil v0.1.0
18-
github.com/fluxcd/pkg/helmtestserver v0.4.0
18+
github.com/fluxcd/pkg/helmtestserver v0.7.0
1919
github.com/fluxcd/pkg/lockedfile v0.1.0
2020
github.com/fluxcd/pkg/runtime v0.13.1
2121
github.com/fluxcd/pkg/ssh v0.2.0
@@ -35,7 +35,7 @@ require (
3535
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
3636
google.golang.org/api v0.62.0
3737
gotest.tools v2.2.0+incompatible
38-
helm.sh/helm/v3 v3.7.2
38+
helm.sh/helm/v3 v3.8.0
3939
k8s.io/api v0.23.3
4040
k8s.io/apimachinery v0.23.3
4141
k8s.io/client-go v0.23.3
@@ -48,13 +48,12 @@ require (
4848
require (
4949
cloud.google.com/go v0.99.0 // indirect
5050
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
51-
github.com/BurntSushi/toml v0.3.1 // indirect
51+
github.com/BurntSushi/toml v0.4.1 // indirect
5252
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
5353
github.com/Masterminds/goutils v1.1.1 // indirect
5454
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
5555
github.com/Masterminds/squirrel v1.5.2 // indirect
5656
github.com/Microsoft/go-winio v0.5.2 // indirect
57-
github.com/Microsoft/hcsshim v0.8.23 // indirect
5857
github.com/PuerkitoBio/purell v1.1.1 // indirect
5958
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
6059
github.com/acomagu/bufpipe v1.0.3 // indirect
@@ -65,13 +64,12 @@ require (
6564
github.com/bugsnag/panicwrap v1.3.4 // indirect
6665
github.com/cespare/xxhash/v2 v2.1.2 // indirect
6766
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
68-
github.com/containerd/containerd v1.5.7 // indirect
69-
github.com/containerd/continuity v0.1.0 // indirect
67+
github.com/containerd/containerd v1.5.9 // indirect
7068
github.com/davecgh/go-spew v1.1.1 // indirect
71-
github.com/docker/cli v20.10.7+incompatible // indirect
69+
github.com/docker/cli v20.10.11+incompatible // indirect
7270
github.com/docker/distribution v2.8.0+incompatible // indirect
73-
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect
74-
github.com/docker/docker-credential-helpers v0.6.3 // indirect
71+
github.com/docker/docker v20.10.12+incompatible // indirect
72+
github.com/docker/docker-credential-helpers v0.6.4 // indirect
7573
github.com/docker/go-connections v0.4.0 // indirect
7674
github.com/docker/go-metrics v0.0.1 // indirect
7775
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
@@ -109,17 +107,17 @@ require (
109107
github.com/imdario/mergo v0.3.12 // indirect
110108
github.com/inconshreveable/mousetrap v1.0.0 // indirect
111109
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
112-
github.com/jmoiron/sqlx v1.3.1 // indirect
110+
github.com/jmoiron/sqlx v1.3.4 // indirect
113111
github.com/josharian/intern v1.0.0 // indirect
114112
github.com/json-iterator/go v1.1.12 // indirect
115113
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
116114
github.com/kevinburke/ssh_config v1.1.0 // indirect
117-
github.com/klauspost/compress v1.13.5 // indirect
115+
github.com/klauspost/compress v1.13.6 // indirect
118116
github.com/klauspost/cpuid v1.3.1 // indirect
119117
github.com/kylelemons/godebug v1.1.0 // indirect
120118
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
121119
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
122-
github.com/lib/pq v1.10.0 // indirect
120+
github.com/lib/pq v1.10.4 // indirect
123121
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
124122
github.com/mailru/easyjson v0.7.6 // indirect
125123
github.com/mattn/go-colorable v0.1.12 // indirect
@@ -128,10 +126,10 @@ require (
128126
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
129127
github.com/minio/md5-simd v1.1.0 // indirect
130128
github.com/minio/sha256-simd v0.1.1 // indirect
131-
github.com/mitchellh/copystructure v1.1.1 // indirect
129+
github.com/mitchellh/copystructure v1.2.0 // indirect
132130
github.com/mitchellh/go-homedir v1.1.0 // indirect
133131
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
134-
github.com/mitchellh/reflectwalk v1.0.1 // indirect
132+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
135133
github.com/moby/locker v1.0.1 // indirect
136134
github.com/moby/spdystream v0.2.0 // indirect
137135
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
@@ -141,7 +139,6 @@ require (
141139
github.com/morikuni/aec v1.0.0 // indirect
142140
github.com/opencontainers/go-digest v1.0.0 // indirect
143141
github.com/opencontainers/image-spec v1.0.2 // indirect
144-
github.com/opencontainers/runc v1.0.2 // indirect
145142
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
146143
github.com/pkg/errors v0.9.1 // indirect
147144
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -181,8 +178,8 @@ require (
181178
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
182179
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
183180
google.golang.org/appengine v1.6.7 // indirect
184-
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
185-
google.golang.org/grpc v1.42.0 // indirect
181+
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
182+
google.golang.org/grpc v1.43.0 // indirect
186183
google.golang.org/protobuf v1.27.1 // indirect
187184
gopkg.in/gorp.v1 v1.7.2 // indirect
188185
gopkg.in/inf.v0 v0.9.1 // indirect
@@ -197,7 +194,7 @@ require (
197194
k8s.io/klog/v2 v2.40.1 // indirect
198195
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
199196
k8s.io/kubectl v0.23.2 // indirect
200-
oras.land/oras-go v0.4.0 // indirect
197+
oras.land/oras-go v1.1.0 // indirect
201198
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
202199
sigs.k8s.io/kustomize/api v0.10.1 // indirect
203200
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect

0 commit comments

Comments
 (0)