Skip to content

Commit c3e0c81

Browse files
committed
add more docs and fix sig verification error reasons
Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent f347630 commit c3e0c81

12 files changed

+150
-45
lines changed

api/v1beta2/condition_types.go

+4
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,8 @@ const (
8080

8181
// SymlinkUpdateFailedReason signals a failure in updating a symlink.
8282
SymlinkUpdateFailedReason string = "SymlinkUpdateFailed"
83+
84+
// VerificationFailedReason signals a failure in verifying the signature of
85+
// an artifact instance, such as a git commit or a helm chart.
86+
VerificationFailedReason string = "VerificationFailed"
8387
)

api/v1beta2/helmchart_types.go

+3
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ type HelmChartSpec struct {
9090
VerificationKeyring *VerificationKeyring `json:"verificationKeyring,omitempty"`
9191
}
9292

93+
// VerificationKeyring contains enough info to get the public GPG key to be used for verifying
94+
// the chart signature using a provenance file.
9395
type VerificationKeyring struct {
96+
// SecretRef is a reference to the secret that contains the public GPG key.
9497
// +required
9598
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
9699

api/v1beta2/zz_generated.deepcopy.go

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

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -405,16 +405,17 @@ spec:
405405
type: string
406406
type: array
407407
verificationKeyring:
408-
description: Keyring information for verifying the packaged chart's
408+
description: VerificationKeyring for verifying the packaged chart's
409409
signature using a provenance file.
410410
properties:
411411
key:
412412
default: pubring.gpg
413-
description: The key that corresponds to the keyring value.
413+
description: Key in the SecretRef that contains the public keyring
414+
in legacy GPG format.
414415
type: string
415416
secretRef:
416-
description: LocalObjectReference contains enough information
417-
to locate the referenced Kubernetes resource object.
417+
description: SecretRef is a reference to the secret that contains
418+
the public GPG key.
418419
properties:
419420
name:
420421
description: Name of the referent.

controllers/gitrepository_controller.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ func (r *GitRepositoryReconciler) verifyCommitSignature(ctx context.Context, obj
636636
if err := r.Client.Get(ctx, publicKeySecret, secret); err != nil {
637637
e := &serror.Event{
638638
Err: fmt.Errorf("PGP public keys secret error: %w", err),
639-
Reason: "VerificationError",
639+
Reason: sourcev1.VerificationFailedReason,
640640
}
641641
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
642642
return sreconcile.ResultEmpty, e

controllers/gitrepository_controller_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ func TestGitRepositoryReconciler_verifyCommitSignature(t *testing.T) {
11941194
},
11951195
wantErr: true,
11961196
assertConditions: []metav1.Condition{
1197-
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, "VerificationError", "PGP public keys secret error: secrets \"none-existing\" not found"),
1197+
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationFailedReason, "PGP public keys secret error: secrets \"none-existing\" not found"),
11981198
},
11991199
},
12001200
{

controllers/helmchart_controller.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -473,17 +473,16 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
473473
if err != nil {
474474
e := &serror.Event{
475475
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
476-
Reason: sourcev1.AuthenticationFailedReason,
476+
Reason: sourcev1.VerificationFailedReason,
477477
}
478-
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
478+
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
479479
return sreconcile.ResultEmpty, e
480480
}
481481
opts.Keyring = keyring
482482

483483
// Build the chart
484484
ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version}
485485
build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts)
486-
487486
if err != nil {
488487
return sreconcile.ResultEmpty, err
489488
}
@@ -607,9 +606,9 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
607606
if err != nil {
608607
e := &serror.Event{
609608
Err: fmt.Errorf("failed to get public key for chart signature verification: %w", err),
610-
Reason: sourcev1.AuthenticationFailedReason,
609+
Reason: sourcev1.VerificationFailedReason,
611610
}
612-
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Error())
611+
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
613612
return sreconcile.ResultEmpty, e
614613
}
615614
opts.Keyring = keyring
@@ -698,10 +697,11 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, obj *source
698697
if b.ProvFilePath != "" {
699698
provArtifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), b.Version, fmt.Sprintf("%s-%s.tgz.prov", b.Name, b.Version))
700699
if err = r.Storage.CopyFromPath(&provArtifact, b.ProvFilePath); err != nil {
701-
return sreconcile.ResultEmpty, &serror.Event{
700+
e := &serror.Event{
702701
Err: fmt.Errorf("unable to copy Helm chart provenance file to storage: %w", err),
703-
Reason: sourcev1.StorageOperationFailedCondition,
702+
Reason: sourcev1.ArchiveOperationFailedReason,
704703
}
704+
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
705705
}
706706
}
707707

@@ -803,14 +803,13 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
803803
localPath := r.Storage.LocalPath(*obj.GetArtifact())
804804
provFilePath := localPath + ".prov"
805805
dir := filepath.Dir(localPath)
806-
callbacks := make([]func(path string, info os.FileInfo) bool, 0)
807-
callbacks = append(callbacks, func(path string, info os.FileInfo) bool {
806+
callback := func(path string, info os.FileInfo) bool {
808807
if path != localPath && path != provFilePath && info.Mode()&os.ModeSymlink != os.ModeSymlink {
809808
return true
810809
}
811810
return false
812-
})
813-
if _, err := r.Storage.RemoveConditionally(dir, callbacks); err != nil {
811+
}
812+
if _, err := r.Storage.RemoveConditionally(dir, callback); err != nil {
814813
return &serror.Event{
815814
Err: fmt.Errorf("garbage collection of old artifacts failed: %w", err),
816815
Reason: "GarbageCollectionFailed",
@@ -1036,11 +1035,13 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
10361035

10371036
if build.VerificationSignature != nil && build.ProvFilePath != "" {
10381037
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))
1038+
sigVerMsg.WriteString(fmt.Sprintf("chart signed by: '%v'", strings.Join(build.VerificationSignature.Identities[:], ",")))
1039+
sigVerMsg.WriteString(fmt.Sprintf(" using key with fingeprint: '%X'", build.VerificationSignature.KeyFingerprint))
1040+
sigVerMsg.WriteString(fmt.Sprintf(" and hash verified: '%s'", build.VerificationSignature.FileHash))
10421041

10431042
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, sigVerMsg.String())
1043+
} else {
1044+
conditions.Delete(obj, sourcev1.SourceVerifiedCondition)
10441045
}
10451046

10461047
if err != nil {

controllers/helmchart_controller_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
551551
g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal(gitArtifact.Revision))
552552
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{
553553
*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"),
554+
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, sourcev1.ChartVerifiedSucceededReason, "chart signed by: 'TestUser' using key with fingeprint: '943CB5929ECDA2B5B5EC88BC7035BA97D32A87C1' and hash verified: 'sha256:007c7b7446eebcb18caeffe9898a3356ba1795f54df40ad39cfcc7382874a10a'"),
555555
}))
556556
},
557557
cleanFunc: func(g *WithT, build *chart.Build) {

controllers/storage.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ type Storage struct {
5353
Timeout time.Duration `json:"timeout"`
5454
}
5555

56+
// removeFileCallback is a function which determines whether the
57+
// provided file should be removed from the filesystem.
58+
type removeFileCallback func(path string, info os.FileInfo) bool
59+
5660
// NewStorage creates the storage helper for a given path and hostname.
5761
func NewStorage(basePath string, hostname string, timeout time.Duration) (*Storage, error) {
5862
if f, err := os.Stat(basePath); os.IsNotExist(err) || !f.IsDir() {
@@ -145,7 +149,9 @@ func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) ([]string, err
145149
return deletedFiles, nil
146150
}
147151

148-
func (s *Storage) RemoveConditionally(dir string, callbacks []func(path string, info os.FileInfo) bool) ([]string, error) {
152+
// RemoveConditionally walks through the provided dir and then deletes all files
153+
// for which any of the callbacks return true.
154+
func (s *Storage) RemoveConditionally(dir string, callbacks ...removeFileCallback) ([]string, error) {
149155
deletedFiles := []string{}
150156
var errors []string
151157
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {

docs/api/source.md

+75
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,20 @@ references to this object.
668668
NOTE: Not implemented, provisional as of <a href="https://github.com/fluxcd/flux2/pull/2092">https://github.com/fluxcd/flux2/pull/2092</a></p>
669669
</td>
670670
</tr>
671+
<tr>
672+
<td>
673+
<code>verificationKeyring</code><br>
674+
<em>
675+
<a href="#source.toolkit.fluxcd.io/v1beta2.VerificationKeyring">
676+
VerificationKeyring
677+
</a>
678+
</em>
679+
</td>
680+
<td>
681+
<em>(Optional)</em>
682+
<p>VerificationKeyring for verifying the packaged chart&rsquo;s signature using a provenance file.</p>
683+
</td>
684+
</tr>
671685
</table>
672686
</td>
673687
</tr>
@@ -1850,6 +1864,20 @@ references to this object.
18501864
NOTE: Not implemented, provisional as of <a href="https://github.com/fluxcd/flux2/pull/2092">https://github.com/fluxcd/flux2/pull/2092</a></p>
18511865
</td>
18521866
</tr>
1867+
<tr>
1868+
<td>
1869+
<code>verificationKeyring</code><br>
1870+
<em>
1871+
<a href="#source.toolkit.fluxcd.io/v1beta2.VerificationKeyring">
1872+
VerificationKeyring
1873+
</a>
1874+
</em>
1875+
</td>
1876+
<td>
1877+
<em>(Optional)</em>
1878+
<p>VerificationKeyring for verifying the packaged chart&rsquo;s signature using a provenance file.</p>
1879+
</td>
1880+
</tr>
18531881
</tbody>
18541882
</table>
18551883
</div>
@@ -2251,6 +2279,53 @@ string
22512279
Source is the interface that provides generic access to the Artifact and
22522280
interval. It must be supported by all kinds of the source.toolkit.fluxcd.io
22532281
API group.</p>
2282+
<h3 id="source.toolkit.fluxcd.io/v1beta2.VerificationKeyring">VerificationKeyring
2283+
</h3>
2284+
<p>
2285+
(<em>Appears on:</em>
2286+
<a href="#source.toolkit.fluxcd.io/v1beta2.HelmChartSpec">HelmChartSpec</a>)
2287+
</p>
2288+
<p>VerificationKeyring contains enough info to get the public GPG key to be used for verifying
2289+
the chart signature using a provenance file.</p>
2290+
<div class="md-typeset__scrollwrap">
2291+
<div class="md-typeset__table">
2292+
<table>
2293+
<thead>
2294+
<tr>
2295+
<th>Field</th>
2296+
<th>Description</th>
2297+
</tr>
2298+
</thead>
2299+
<tbody>
2300+
<tr>
2301+
<td>
2302+
<code>secretRef</code><br>
2303+
<em>
2304+
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
2305+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
2306+
</a>
2307+
</em>
2308+
</td>
2309+
<td>
2310+
<p>SecretRef is a reference to the secret that contains the public GPG key.</p>
2311+
</td>
2312+
</tr>
2313+
<tr>
2314+
<td>
2315+
<code>key</code><br>
2316+
<em>
2317+
string
2318+
</em>
2319+
</td>
2320+
<td>
2321+
<em>(Optional)</em>
2322+
<p>Key in the SecretRef that contains the public keyring in legacy GPG format.</p>
2323+
</td>
2324+
</tr>
2325+
</tbody>
2326+
</table>
2327+
</div>
2328+
</div>
22542329
<div class="admonition note">
22552330
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
22562331
</div>

internal/helm/chart/builder.go

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

108-
// Keyring can be configured with the bytes of a public kering in legacy
108+
// Keyring can be set to the bytes of a public kering in legacy
109109
// PGP format used for verifying a chart's signature using a provenance file.
110110
Keyring []byte
111111
}
@@ -130,11 +130,11 @@ type Build struct {
130130
// Can be empty, in which case a failure should be assumed.
131131
Path string
132132
// ProvFilePath is the absolute path to a provenance file.
133-
// Can be empty, in which case it should be assumed that the packaged
133+
// It can be empty, in which case it should be assumed that the packaged
134134
// chart is not verified.
135135
ProvFilePath string
136136
// VerificationSignature is populated when a chart's signature
137-
// is successfully verified using it's provenance file.
137+
// is successfully verified using its provenance file.
138138
VerificationSignature *VerificationSignature
139139
// ValuesFiles is the list of files used to compose the chart's
140140
// default "values.yaml".

internal/util/file.go

+14-20
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,32 @@ import (
2222
"path/filepath"
2323
)
2424

25-
func writeBytesToFile(bytes []byte, out string, temp bool) (*os.File, error) {
26-
var file *os.File
27-
var err error
28-
29-
if temp {
30-
file, err = os.CreateTemp("", out)
31-
if err != nil {
32-
return nil, fmt.Errorf("failed to create temporary file %s: %w", filepath.Base(out), err)
33-
}
34-
} else {
35-
file, err = os.Create(out)
36-
if err != nil {
37-
return nil, fmt.Errorf("failed to create temporary file for chart %s: %w", out, err)
38-
}
39-
}
25+
func writeBytesToFile(bytes []byte, file *os.File) error {
4026
if _, err := file.Write(bytes); err != nil {
4127
_ = file.Close()
42-
return nil, fmt.Errorf("failed to write to file %s: %w", file.Name(), err)
28+
return fmt.Errorf("failed to write to file %s: %w", file.Name(), err)
4329
}
4430
if err := file.Close(); err != nil {
45-
return nil, err
31+
return err
4632
}
47-
return file, nil
33+
return nil
4834
}
4935

5036
// Writes the provided bytes to a file at the given path and returns the file handle.
5137
func WriteToFile(bytes []byte, path string) (*os.File, error) {
52-
return writeBytesToFile(bytes, path, false)
38+
file, err := os.Create(path)
39+
if err != nil {
40+
return nil, fmt.Errorf("failed to create temporary file for chart %s: %w", path, err)
41+
}
42+
return file, writeBytesToFile(bytes, file)
5343
}
5444

5545
// Writes the provided bytes to a temp file with the name provided in the path and
5646
// returns the file handle.
5747
func WriteToTempFile(bytes []byte, out string) (*os.File, error) {
58-
return writeBytesToFile(bytes, filepath.Base(out), true)
48+
file, err := os.CreateTemp("", filepath.Base(out))
49+
if err != nil {
50+
return nil, fmt.Errorf("failed to create temporary file %s: %w", filepath.Base(out), err)
51+
}
52+
return file, writeBytesToFile(bytes, file)
5953
}

0 commit comments

Comments
 (0)