Skip to content

Commit f347630

Browse files
committed
make doc changes and fix reconilation process
Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent ae68b45 commit f347630

File tree

8 files changed

+155
-105
lines changed

8 files changed

+155
-105
lines changed

api/v1beta2/helmchart_types.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ type HelmChartSpec struct {
8585
// +optional
8686
AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"`
8787

88-
// Keyring information for verifying the packaged chart's signature using a provenance file.
88+
// VerificationKeyring for verifying the packaged chart's signature using a provenance file.
8989
// +optional
9090
VerificationKeyring *VerificationKeyring `json:"verificationKeyring,omitempty"`
9191
}
@@ -94,7 +94,7 @@ type VerificationKeyring struct {
9494
// +required
9595
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
9696

97-
// The key that corresponds to the keyring value.
97+
// Key in the SecretRef that contains the public keyring in legacy GPG format.
9898
// +kubebuilder:default:=pubring.gpg
9999
// +optional
100100
Key string `json:"key,omitempty"`
@@ -168,6 +168,10 @@ const (
168168
// ChartPackageSucceededReason signals that the package of the Helm
169169
// chart succeeded.
170170
ChartPackageSucceededReason string = "ChartPackageSucceeded"
171+
172+
// ChartVerifiedSucceededReason signals that the Helm chart's signature
173+
// has been verified using it's provenance file.
174+
ChartVerifiedSucceededReason string = "ChartVerifiedSucceeded"
171175
)
172176

173177
// GetConditions returns the status conditions of the object.

controllers/helmchart_controller.go

+16-33
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ var helmChartReadyCondition = summarize.Conditions{
9797
},
9898
}
9999

100-
const KeyringFileName = "pubring.gpg"
101-
102100
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts,verbs=get;list;watch;create;update;patch;delete
103101
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/status,verbs=get;update;patch
104102
// +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 *
475473
if err != nil {
476474
e := &serror.Event{
477475
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
478-
Reason: sourcev1.SourceVerifiedCondition,
476+
Reason: sourcev1.AuthenticationFailedReason,
479477
}
480-
conditions.MarkFalse(obj, sourcev1.FetchFailedCondition, sourcev1.SourceVerifiedCondition, e.Error())
478+
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
481479
return sreconcile.ResultEmpty, e
482480
}
483481
opts.Keyring = keyring
@@ -609,9 +607,9 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
609607
if err != nil {
610608
e := &serror.Event{
611609
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
612-
Reason: sourcev1.SourceVerifiedCondition,
610+
Reason: sourcev1.AuthenticationFailedReason,
613611
}
614-
conditions.MarkFalse(obj, sourcev1.FetchFailedCondition, sourcev1.SourceVerifiedCondition, e.Error())
612+
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
615613
return sreconcile.ResultEmpty, e
616614
}
617615
opts.Keyring = keyring
@@ -651,14 +649,6 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
651649
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
652650
conditions.MarkTrue(obj, meta.ReadyCondition, reasonForBuild(b), b.Summary())
653651
}
654-
if b.VerificationSignature != nil && b.ProvFilePath != "" && obj.GetArtifact() != nil {
655-
var sigVerMsg strings.Builder
656-
sigVerMsg.WriteString(fmt.Sprintf("chart signed by: %v", strings.Join(b.VerificationSignature.Identities[:], ",")))
657-
sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: %X", b.VerificationSignature.KeyFingerprint))
658-
sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: %s", b.VerificationSignature.FileHash))
659-
660-
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, reasonForBuild(b), sigVerMsg.String())
661-
}
662652
}()
663653

664654
// Create artifact from build data
@@ -914,22 +904,6 @@ func (r *HelmChartReconciler) getHelmRepositorySecret(ctx context.Context, repos
914904
return &secret, nil
915905
}
916906

917-
func (r *HelmChartReconciler) getVerificationKeyringSecret(ctx context.Context, chart *sourcev1.HelmChart) (*corev1.Secret, error) {
918-
if chart.Spec.VerificationKeyring == nil {
919-
return nil, nil
920-
}
921-
name := types.NamespacedName{
922-
Namespace: chart.GetNamespace(),
923-
Name: chart.Spec.VerificationKeyring.SecretRef.Name,
924-
}
925-
var secret corev1.Secret
926-
err := r.Client.Get(ctx, name, &secret)
927-
if err != nil {
928-
return nil, err
929-
}
930-
return &secret, nil
931-
}
932-
933907
func (r *HelmChartReconciler) indexHelmRepositoryByURL(o client.Object) []string {
934908
repo, ok := o.(*sourcev1.HelmRepository)
935909
if !ok {
@@ -1060,6 +1034,15 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
10601034
conditions.Delete(obj, sourcev1.BuildFailedCondition)
10611035
}
10621036

1037+
if build.VerificationSignature != nil && build.ProvFilePath != "" {
1038+
var sigVerMsg strings.Builder
1039+
sigVerMsg.WriteString(fmt.Sprintf("chart signed by: %v", strings.Join(build.VerificationSignature.Identities[:], ",")))
1040+
sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: %X", build.VerificationSignature.KeyFingerprint))
1041+
sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: %s", build.VerificationSignature.FileHash))
1042+
1043+
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, sigVerMsg.String())
1044+
}
1045+
10631046
if err != nil {
10641047
var buildErr *chart.BuildError
10651048
if ok := errors.As(err, &buildErr); !ok {
@@ -1105,10 +1088,10 @@ func (r *HelmChartReconciler) getProvenanceKeyring(ctx context.Context, chart *s
11051088
return nil, err
11061089
}
11071090
key := chart.Spec.VerificationKeyring.Key
1108-
if val, ok := secret.Data[key]; !ok {
1091+
val, ok := secret.Data[key]
1092+
if !ok {
11091093
err = fmt.Errorf("secret doesn't contain the advertised verification keyring name %s", key)
11101094
return nil, err
1111-
} else {
1112-
return val, nil
11131095
}
1096+
return val, nil
11141097
}

controllers/helmchart_controller_test.go

+76-55
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import (
5252
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
5353
)
5454

55-
const publicKeyFileName = "pub.pgp"
55+
const publicKeyFileName = "pub.gpg"
5656

5757
func TestHelmChartReconciler_Reconcile(t *testing.T) {
5858
g := NewWithT(t)
@@ -458,14 +458,19 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
458458
}
459459
g.Expect(storage.Archive(gitArtifact, "testdata/charts", nil)).To(Succeed())
460460

461+
keyring, err := os.ReadFile("testdata/charts/pub.gpg")
462+
g.Expect(err).ToNot(HaveOccurred())
463+
g.Expect(keyring).ToNot(BeEmpty())
464+
461465
tests := []struct {
462-
name string
463-
source sourcev1.Source
464-
beforeFunc func(obj *sourcev1.HelmChart)
465-
want sreconcile.Result
466-
wantErr error
467-
assertFunc func(g *WithT, build chart.Build, obj sourcev1.HelmChart)
468-
cleanFunc func(g *WithT, build *chart.Build)
466+
name string
467+
source sourcev1.Source
468+
keyringSecret *corev1.Secret
469+
beforeFunc func(obj *sourcev1.HelmChart)
470+
want sreconcile.Result
471+
wantErr error
472+
assertFunc func(g *WithT, build chart.Build, obj sourcev1.HelmChart)
473+
cleanFunc func(g *WithT, build *chart.Build)
469474
}{
470475
{
471476
name: "Observes Artifact revision and build result",
@@ -501,6 +506,59 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
501506
g.Expect(os.Remove(build.Path)).To(Succeed())
502507
},
503508
},
509+
{
510+
name: "Observes Artifact revision and build result with valid signature",
511+
source: &sourcev1.GitRepository{
512+
ObjectMeta: metav1.ObjectMeta{
513+
Name: "gitrepository",
514+
Namespace: "default",
515+
},
516+
Status: sourcev1.GitRepositoryStatus{
517+
Artifact: gitArtifact,
518+
},
519+
},
520+
keyringSecret: &corev1.Secret{
521+
ObjectMeta: metav1.ObjectMeta{
522+
Name: "keyring-secret",
523+
Namespace: "default",
524+
},
525+
Data: map[string][]byte{
526+
publicKeyFileName: keyring,
527+
},
528+
},
529+
beforeFunc: func(obj *sourcev1.HelmChart) {
530+
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
531+
obj.Spec.SourceRef = sourcev1.LocalHelmChartSourceReference{
532+
Name: "gitrepository",
533+
Kind: sourcev1.GitRepositoryKind,
534+
}
535+
obj.Spec.VerificationKeyring = &sourcev1.VerificationKeyring{
536+
SecretRef: meta.LocalObjectReference{
537+
Name: "keyring-secret",
538+
},
539+
Key: publicKeyFileName,
540+
}
541+
},
542+
want: sreconcile.ResultSuccess,
543+
assertFunc: func(g *WithT, build chart.Build, obj sourcev1.HelmChart) {
544+
g.Expect(build.Complete()).To(BeTrue())
545+
g.Expect(build.Name).To(Equal("helmchart"))
546+
g.Expect(build.Version).To(Equal("0.1.0"))
547+
g.Expect(build.Path).To(BeARegularFile())
548+
g.Expect(build.VerificationSignature).ToNot(BeNil())
549+
g.Expect(build.ProvFilePath).To(BeARegularFile())
550+
551+
g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal(gitArtifact.Revision))
552+
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
553+
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewChart", "pulled 'helmchart' chart with version '0.1.0'"),
554+
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, "chart signed by: TestUser using key with fingeprint: 943CB5929ECDA2B5B5EC88BC7035BA97D32A87C1 and hash verified: sha256:007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a"),
555+
}))
556+
},
557+
cleanFunc: func(g *WithT, build *chart.Build) {
558+
g.Expect(os.Remove(build.Path)).To(Succeed())
559+
g.Expect(os.Remove(build.ProvFilePath)).To(Succeed())
560+
},
561+
},
504562
{
505563
name: "Error on unavailable source",
506564
beforeFunc: func(obj *sourcev1.HelmChart) {
@@ -605,6 +663,9 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
605663
if tt.source != nil {
606664
clientBuilder.WithRuntimeObjects(tt.source)
607665
}
666+
if tt.keyringSecret != nil {
667+
clientBuilder.WithRuntimeObjects(tt.keyringSecret)
668+
}
608669

609670
r := &HelmChartReconciler{
610671
Client: clientBuilder.Build(),
@@ -1129,7 +1190,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
11291190
},
11301191
{
11311192
name: "Copying artifact to storage from build makes Ready=True",
1132-
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
1193+
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
11331194
beforeFunc: func(obj *sourcev1.HelmChart) {
11341195
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
11351196
},
@@ -1145,24 +1206,6 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
11451206
*conditions.TrueCondition(meta.ReadyCondition, sourcev1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
11461207
},
11471208
},
1148-
{
1149-
name: "Build with a verified signature sets SourceVerifiedCondition=Truue",
1150-
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", "testdata/charts/helmchart-0.1.0.tgz.prov"),
1151-
beforeFunc: func(obj *sourcev1.HelmChart) {
1152-
obj.Status.Artifact = &sourcev1.Artifact{
1153-
Path: "testdata/charts/helmchart-0.1.0.tgz",
1154-
}
1155-
},
1156-
want: sreconcile.ResultSuccess,
1157-
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
1158-
provArtifact := testStorage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "0.1.0", "helmchart-0.1.0.tgz.prov")
1159-
t.Expect(provArtifact.Path).ToNot(BeEmpty())
1160-
},
1161-
assertConditions: []metav1.Condition{
1162-
*conditions.TrueCondition(meta.ReadyCondition, sourcev1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
1163-
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartPullSucceededReason, "chart signed by: TestUser1,TestUser2 using key with fingeprint: 0102000000000000000000000000000000000000 and hash verified: 53gntj23r24asnf0"),
1164-
},
1165-
},
11661209
{
11671210
name: "Up-to-date chart build does not persist artifact to storage",
11681211
build: &chart.Build{
@@ -1208,7 +1251,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
12081251
},
12091252
{
12101253
name: "Removes ArtifactOutdatedCondition after creating new artifact",
1211-
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
1254+
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
12121255
beforeFunc: func(obj *sourcev1.HelmChart) {
12131256
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
12141257
},
@@ -1226,7 +1269,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
12261269
},
12271270
{
12281271
name: "Creates latest symlink to the created artifact",
1229-
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", ""),
1272+
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
12301273
afterFunc: func(t *WithT, obj *sourcev1.HelmChart) {
12311274
t.Expect(obj.GetArtifact()).ToNot(BeNil())
12321275

@@ -1726,10 +1769,8 @@ func TestHelmChartReconciler_reconcileSubRecs(t *testing.T) {
17261769
}
17271770
}
17281771

1729-
func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
1772+
func mockChartBuild(name, version, path string) *chart.Build {
17301773
var copyP string
1731-
var copyPP string
1732-
var verSig *chart.VerificationSignature
17331774
if path != "" {
17341775
f, err := os.Open(path)
17351776
if err == nil {
@@ -1743,29 +1784,9 @@ func mockChartBuild(name, version, path, provFilePath string) *chart.Build {
17431784
}
17441785
}
17451786
}
1746-
if provFilePath != "" {
1747-
f, err := os.Open(provFilePath)
1748-
if err == nil {
1749-
defer f.Close()
1750-
ff, err := os.CreateTemp("", "chart-mock-*.tgz.prov")
1751-
if err == nil {
1752-
defer ff.Close()
1753-
if _, err = io.Copy(ff, f); err == nil {
1754-
copyPP = ff.Name()
1755-
}
1756-
}
1757-
verSig = &chart.VerificationSignature{
1758-
FileHash: "53gntj23r24asnf0",
1759-
Identities: []string{"TestUser1", "TestUser2"},
1760-
KeyFingerprint: [20]byte{1, 2},
1761-
}
1762-
}
1763-
}
17641787
return &chart.Build{
1765-
Name: name,
1766-
Version: version,
1767-
Path: copyP,
1768-
ProvFilePath: copyPP,
1769-
VerificationSignature: verSig,
1788+
Name: name,
1789+
Version: version,
1790+
Path: copyP,
17701791
}
17711792
}

internal/helm/chart/builder.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ type BuildOptions struct {
105105
// because the list of ValuesFiles has changed.
106106
Force bool
107107

108-
// Keyring can be set to the data of the chart VerificationKeyring secret
109-
// used for verifying a chart's signature using a provenance file.
108+
// Keyring can be configured with the bytes of a public kering in legacy
109+
// PGP format used for verifying a chart's signature using a provenance file.
110110
Keyring []byte
111111
}
112112

@@ -134,7 +134,7 @@ type Build struct {
134134
// chart is not verified.
135135
ProvFilePath string
136136
// VerificationSignature is populated when a chart's signature
137-
// is susccessfully verified using it's provenance file.
137+
// is successfully verified using it's provenance file.
138138
VerificationSignature *VerificationSignature
139139
// ValuesFiles is the list of files used to compose the chart's
140140
// default "values.yaml".

internal/helm/chart/builder_local.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,11 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
137137
// package the chart ourselves, and instead stored it as is.
138138
if !requiresPackaging {
139139
provFilePath = provenanceFilePath(opts.CachedChart)
140-
if ver, err := verifyProvFile(opts.CachedChart, provFilePath); err != nil {
140+
ver, err := verifyProvFile(opts.CachedChart, provFilePath)
141+
if err != nil {
141142
return nil, err
142-
} else {
143+
}
144+
if ver != nil {
143145
result.VerificationSignature = buildVerificationSig(ver)
144146
result.ProvFilePath = provFilePath
145147
}
@@ -163,9 +165,11 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
163165
if err = copyFileToPath(provenanceFilePath(localRef.Path), provFilePath); err != nil {
164166
return result, &BuildError{Reason: ErrChartPull, Err: err}
165167
}
166-
if ver, err := verifyProvFile(localRef.Path, provFilePath); err != nil {
167-
return result, err
168-
} else {
168+
ver, err := verifyProvFile(localRef.Path, provFilePath)
169+
if err != nil {
170+
return nil, err
171+
}
172+
if ver != nil {
169173
result.ProvFilePath = provFilePath
170174
result.VerificationSignature = buildVerificationSig(ver)
171175
}

0 commit comments

Comments
 (0)