@@ -28,6 +28,7 @@ import (
28
28
"strings"
29
29
"time"
30
30
31
+ soci "github.com/fluxcd/source-controller/internal/oci"
31
32
helmgetter "helm.sh/helm/v3/pkg/getter"
32
33
helmreg "helm.sh/helm/v3/pkg/registry"
33
34
corev1 "k8s.io/api/core/v1"
@@ -57,6 +58,7 @@ import (
57
58
"github.com/fluxcd/pkg/runtime/predicates"
58
59
"github.com/fluxcd/pkg/untar"
59
60
"github.com/google/go-containerregistry/pkg/authn"
61
+ "github.com/google/go-containerregistry/pkg/v1/remote"
60
62
61
63
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
62
64
"github.com/fluxcd/source-controller/internal/cache"
@@ -70,6 +72,8 @@ import (
70
72
"github.com/fluxcd/source-controller/internal/util"
71
73
)
72
74
75
+ const cosignSignatureKey = "cosign.pub"
76
+
73
77
// helmChartReadyCondition contains all the conditions information
74
78
// needed for HelmChart Ready status conditions summary calculation.
75
79
var helmChartReadyCondition = summarize.Conditions {
@@ -80,6 +84,7 @@ var helmChartReadyCondition = summarize.Conditions{
80
84
sourcev1 .BuildFailedCondition ,
81
85
sourcev1 .ArtifactOutdatedCondition ,
82
86
sourcev1 .ArtifactInStorageCondition ,
87
+ sourcev1 .SourceVerifiedCondition ,
83
88
meta .ReadyCondition ,
84
89
meta .ReconcilingCondition ,
85
90
meta .StalledCondition ,
@@ -90,6 +95,7 @@ var helmChartReadyCondition = summarize.Conditions{
90
95
sourcev1 .BuildFailedCondition ,
91
96
sourcev1 .ArtifactOutdatedCondition ,
92
97
sourcev1 .ArtifactInStorageCondition ,
98
+ sourcev1 .SourceVerifiedCondition ,
93
99
meta .StalledCondition ,
94
100
meta .ReconcilingCondition ,
95
101
},
@@ -563,17 +569,38 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
563
569
}()
564
570
}
565
571
572
+ var verifier soci.Verifier
573
+ if obj .Spec .Verify != nil {
574
+ provider := obj .Spec .Verify .Provider
575
+ verifier , err = r .makeVerifier (ctx , obj , authenticator , keychain )
576
+ if err != nil {
577
+ if obj .Spec .Verify .SecretRef == nil {
578
+ provider = fmt .Sprintf ("%s keyless" , provider )
579
+ }
580
+ e := serror .NewGeneric (
581
+ fmt .Errorf ("failed to verify the signature using provider '%s': %w" , provider , err ),
582
+ sourcev1 .VerificationError ,
583
+ )
584
+ conditions .MarkFalse (obj , sourcev1 .SourceVerifiedCondition , e .Reason , e .Err .Error ())
585
+ return sreconcile .ResultEmpty , e
586
+ }
587
+ }
588
+
566
589
// Tell the chart repository to use the OCI client with the configured getter
567
590
clientOpts = append (clientOpts , helmgetter .WithRegistryClient (registryClient ))
568
- ociChartRepo , err := repository .NewOCIChartRepository (normalizedURL , repository .WithOCIGetter (r .Getters ), repository .WithOCIGetterOptions (clientOpts ), repository .WithOCIRegistryClient (registryClient ))
591
+ ociChartRepo , err := repository .NewOCIChartRepository (normalizedURL ,
592
+ repository .WithOCIGetter (r .Getters ),
593
+ repository .WithOCIGetterOptions (clientOpts ),
594
+ repository .WithOCIRegistryClient (registryClient ),
595
+ repository .WithVerifier (verifier ))
569
596
if err != nil {
570
597
return chartRepoConfigErrorReturn (err , obj )
571
598
}
572
599
chartRepo = ociChartRepo
573
600
574
601
// If login options are configured, use them to login to the registry
575
602
// The OCIGetter will later retrieve the stored credentials to pull the chart
576
- if keychain != nil {
603
+ if loginOpt != nil {
577
604
err = ociChartRepo .Login (loginOpt )
578
605
if err != nil {
579
606
e := & serror.Event {
@@ -621,6 +648,17 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
621
648
opts := chart.BuildOptions {
622
649
ValuesFiles : obj .GetValuesFiles (),
623
650
Force : obj .Generation != obj .Status .ObservedGeneration ,
651
+ // The remote builder will not attempt to download the chart if
652
+ // an artifact exist with the same name and version and the force is false.
653
+ // It will try to verify the chart if:
654
+ // - we are on the first reconciliation
655
+ // - the HelmChart spec has changed (generation drift)
656
+ // - the previous reconciliation resulted in a failed artifact verification
657
+ // - there is no artifact in storage
658
+ Verify : obj .Spec .Verify != nil && (obj .Generation <= 0 ||
659
+ conditions .GetObservedGeneration (obj , sourcev1 .SourceVerifiedCondition ) != obj .Generation ||
660
+ conditions .IsFalse (obj , sourcev1 .SourceVerifiedCondition ) ||
661
+ obj .GetArtifact () == nil ),
624
662
}
625
663
if artifact := obj .GetArtifact (); artifact != nil {
626
664
opts .CachedChart = r .Storage .LocalPath (* artifact )
@@ -1029,7 +1067,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1029
1067
1030
1068
// If login options are configured, use them to login to the registry
1031
1069
// The OCIGetter will later retrieve the stored credentials to pull the chart
1032
- if keychain != nil {
1070
+ if loginOpt != nil {
1033
1071
err = ociChartRepo .Login (loginOpt )
1034
1072
if err != nil {
1035
1073
errs = append (errs , fmt .Errorf ("failed to login to OCI chart repository for HelmRepository '%s': %w" , repo .Name , err ))
@@ -1238,6 +1276,11 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
1238
1276
if build .Complete () {
1239
1277
conditions .Delete (obj , sourcev1 .FetchFailedCondition )
1240
1278
conditions .Delete (obj , sourcev1 .BuildFailedCondition )
1279
+ conditions .MarkTrue (obj , sourcev1 .SourceVerifiedCondition , meta .SucceededReason , fmt .Sprintf ("verified signature of version %s" , build .Version ))
1280
+ }
1281
+
1282
+ if obj .Spec .Verify == nil {
1283
+ conditions .Delete (obj , sourcev1 .SourceVerifiedCondition )
1241
1284
}
1242
1285
1243
1286
if err != nil {
@@ -1253,6 +1296,10 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
1253
1296
case chart .ErrChartMetadataPatch , chart .ErrValuesFilesMerge , chart .ErrDependencyBuild , chart .ErrChartPackage :
1254
1297
conditions .Delete (obj , sourcev1 .FetchFailedCondition )
1255
1298
conditions .MarkTrue (obj , sourcev1 .BuildFailedCondition , buildErr .Reason .Reason , buildErr .Error ())
1299
+ case chart .ErrChartVerification :
1300
+ conditions .Delete (obj , sourcev1 .FetchFailedCondition )
1301
+ conditions .MarkTrue (obj , sourcev1 .BuildFailedCondition , buildErr .Reason .Reason , buildErr .Error ())
1302
+ conditions .MarkFalse (obj , sourcev1 .SourceVerifiedCondition , buildErr .Reason .Reason , buildErr .Error ())
1256
1303
default :
1257
1304
conditions .Delete (obj , sourcev1 .BuildFailedCondition )
1258
1305
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , buildErr .Reason .Reason , buildErr .Error ())
@@ -1289,3 +1336,51 @@ func chartRepoConfigErrorReturn(err error, obj *sourcev1.HelmChart) (sreconcile.
1289
1336
return sreconcile .ResultEmpty , e
1290
1337
}
1291
1338
}
1339
+
1340
+ // getVerifyOptions returns the verify options for the given chart.
1341
+ func (r * HelmChartReconciler ) makeVerifier (ctx context.Context , obj * sourcev1.HelmChart , auth authn.Authenticator , keychain authn.Keychain ) (soci.Verifier , error ) {
1342
+ var publicKey []byte
1343
+ verifyOpts := []remote.Option {}
1344
+ if auth != nil {
1345
+ verifyOpts = append (verifyOpts , remote .WithAuth (auth ))
1346
+ } else {
1347
+ verifyOpts = append (verifyOpts , remote .WithAuthFromKeychain (keychain ))
1348
+ }
1349
+
1350
+ // get the public keys from the given secret
1351
+ if secretRef := obj .Spec .Verify .SecretRef ; secretRef != nil {
1352
+ certSecretName := types.NamespacedName {
1353
+ Namespace : obj .Namespace ,
1354
+ Name : secretRef .Name ,
1355
+ }
1356
+
1357
+ var pubSecret corev1.Secret
1358
+ if err := r .Get (ctx , certSecretName , & pubSecret ); err != nil {
1359
+ return nil , err
1360
+ }
1361
+
1362
+ switch obj .Spec .Verify .Provider {
1363
+ case "cosign" :
1364
+ // we expect to find this key in the secret for cosign verification. We want to avoid
1365
+ // having to look for a random public key.
1366
+ if key , ok := pubSecret .Data [cosignSignatureKey ]; ok {
1367
+ publicKey = key
1368
+ }
1369
+ default :
1370
+ }
1371
+ }
1372
+
1373
+ switch obj .Spec .Verify .Provider {
1374
+ case "cosign" :
1375
+ defaultCosignOciOpts := []soci.Options {
1376
+ soci .WithRemoteOptions (verifyOpts ... ),
1377
+ }
1378
+ verifier , err := soci .NewCosignVerifier (ctx , append (defaultCosignOciOpts , soci .WithPublicKey (publicKey ))... )
1379
+ if err != nil {
1380
+ return nil , err
1381
+ }
1382
+ return verifier , nil
1383
+ default :
1384
+ return nil , fmt .Errorf ("unsupported verification provider: %s" , obj .Spec .Verify .Provider )
1385
+ }
1386
+ }
0 commit comments