@@ -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"
@@ -80,6 +82,7 @@ var helmChartReadyCondition = summarize.Conditions{
80
82
sourcev1 .BuildFailedCondition ,
81
83
sourcev1 .ArtifactOutdatedCondition ,
82
84
sourcev1 .ArtifactInStorageCondition ,
85
+ sourcev1 .SourceVerifiedCondition ,
83
86
meta .ReadyCondition ,
84
87
meta .ReconcilingCondition ,
85
88
meta .StalledCondition ,
@@ -90,6 +93,7 @@ var helmChartReadyCondition = summarize.Conditions{
90
93
sourcev1 .BuildFailedCondition ,
91
94
sourcev1 .ArtifactOutdatedCondition ,
92
95
sourcev1 .ArtifactInStorageCondition ,
96
+ sourcev1 .SourceVerifiedCondition ,
93
97
meta .StalledCondition ,
94
98
meta .ReconcilingCondition ,
95
99
},
@@ -564,17 +568,38 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
564
568
}()
565
569
}
566
570
571
+ var verifiers []soci.Verifier
572
+ if obj .Spec .Verify != nil {
573
+ provider := obj .Spec .Verify .Provider
574
+ verifiers , err = r .makeVerifiers (ctx , obj , authenticator , keychain )
575
+ if err != nil {
576
+ if obj .Spec .Verify .SecretRef == nil {
577
+ provider = fmt .Sprintf ("%s keyless" , provider )
578
+ }
579
+ e := serror .NewGeneric (
580
+ fmt .Errorf ("failed to verify the signature using provider '%s': %w" , provider , err ),
581
+ sourcev1 .VerificationError ,
582
+ )
583
+ conditions .MarkFalse (obj , sourcev1 .SourceVerifiedCondition , e .Reason , e .Err .Error ())
584
+ return sreconcile .ResultEmpty , e
585
+ }
586
+ }
587
+
567
588
// Tell the chart repository to use the OCI client with the configured getter
568
589
clientOpts = append (clientOpts , helmgetter .WithRegistryClient (registryClient ))
569
- ociChartRepo , err := repository .NewOCIChartRepository (normalizedURL , repository .WithOCIGetter (r .Getters ), repository .WithOCIGetterOptions (clientOpts ), repository .WithOCIRegistryClient (registryClient ))
590
+ ociChartRepo , err := repository .NewOCIChartRepository (normalizedURL ,
591
+ repository .WithOCIGetter (r .Getters ),
592
+ repository .WithOCIGetterOptions (clientOpts ),
593
+ repository .WithOCIRegistryClient (registryClient ),
594
+ repository .WithVerifiers (verifiers ))
570
595
if err != nil {
571
596
return chartRepoConfigErrorReturn (err , obj )
572
597
}
573
598
chartRepo = ociChartRepo
574
599
575
600
// If login options are configured, use them to login to the registry
576
601
// The OCIGetter will later retrieve the stored credentials to pull the chart
577
- if keychain != nil {
602
+ if loginOpt != nil {
578
603
err = ociChartRepo .Login (loginOpt )
579
604
if err != nil {
580
605
e := & serror.Event {
@@ -622,6 +647,17 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
622
647
opts := chart.BuildOptions {
623
648
ValuesFiles : obj .GetValuesFiles (),
624
649
Force : obj .Generation != obj .Status .ObservedGeneration ,
650
+ // The remote builder will not attempt to download the chart if
651
+ // an artifact exist with the same name and version and the force is false.
652
+ // It will try to verify the chart if:
653
+ // - we are on the first reconciliation
654
+ // - the HelmChart spec has changed (generation drift)
655
+ // - the previous reconciliation resulted in a failed artifact verification
656
+ // - there is no artifact in storage
657
+ Verify : obj .Spec .Verify != nil && (obj .Generation <= 0 ||
658
+ conditions .GetObservedGeneration (obj , sourcev1 .SourceVerifiedCondition ) != obj .Generation ||
659
+ conditions .IsFalse (obj , sourcev1 .SourceVerifiedCondition ) ||
660
+ obj .GetArtifact () == nil ),
625
661
}
626
662
if artifact := obj .GetArtifact (); artifact != nil {
627
663
opts .CachedChart = r .Storage .LocalPath (* artifact )
@@ -1030,7 +1066,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1030
1066
1031
1067
// If login options are configured, use them to login to the registry
1032
1068
// The OCIGetter will later retrieve the stored credentials to pull the chart
1033
- if keychain != nil {
1069
+ if loginOpt != nil {
1034
1070
err = ociChartRepo .Login (loginOpt )
1035
1071
if err != nil {
1036
1072
errs = append (errs , fmt .Errorf ("failed to login to OCI chart repository for HelmRepository '%s': %w" , repo .Name , err ))
@@ -1239,6 +1275,11 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
1239
1275
if build .Complete () {
1240
1276
conditions .Delete (obj , sourcev1 .FetchFailedCondition )
1241
1277
conditions .Delete (obj , sourcev1 .BuildFailedCondition )
1278
+ conditions .MarkTrue (obj , sourcev1 .SourceVerifiedCondition , meta .SucceededReason , fmt .Sprintf ("verified signature of version %s" , build .Version ))
1279
+ }
1280
+
1281
+ if obj .Spec .Verify == nil {
1282
+ conditions .Delete (obj , sourcev1 .SourceVerifiedCondition )
1242
1283
}
1243
1284
1244
1285
if err != nil {
@@ -1251,7 +1292,7 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
1251
1292
}
1252
1293
1253
1294
switch buildErr .Reason {
1254
- case chart .ErrChartMetadataPatch , chart .ErrValuesFilesMerge , chart .ErrDependencyBuild , chart .ErrChartPackage :
1295
+ case chart .ErrChartMetadataPatch , chart .ErrValuesFilesMerge , chart .ErrDependencyBuild , chart .ErrChartPackage , chart . ErrChartVerification :
1255
1296
conditions .Delete (obj , sourcev1 .FetchFailedCondition )
1256
1297
conditions .MarkTrue (obj , sourcev1 .BuildFailedCondition , buildErr .Reason .Reason , buildErr .Error ())
1257
1298
default :
@@ -1290,3 +1331,60 @@ func chartRepoConfigErrorReturn(err error, obj *sourcev1.HelmChart) (sreconcile.
1290
1331
return sreconcile .ResultEmpty , e
1291
1332
}
1292
1333
}
1334
+
1335
+ // makeVerifiers returns a list of verifiers for the given chart.
1336
+ func (r * HelmChartReconciler ) makeVerifiers (ctx context.Context , obj * sourcev1.HelmChart , auth authn.Authenticator , keychain authn.Keychain ) ([]soci.Verifier , error ) {
1337
+ var verifiers []soci.Verifier
1338
+ verifyOpts := []remote.Option {}
1339
+ if auth != nil {
1340
+ verifyOpts = append (verifyOpts , remote .WithAuth (auth ))
1341
+ } else {
1342
+ verifyOpts = append (verifyOpts , remote .WithAuthFromKeychain (keychain ))
1343
+ }
1344
+
1345
+ switch obj .Spec .Verify .Provider {
1346
+ case "cosign" :
1347
+ defaultCosignOciOpts := []soci.Options {
1348
+ soci .WithRemoteOptions (verifyOpts ... ),
1349
+ }
1350
+
1351
+ // get the public keys from the given secret
1352
+ if secretRef := obj .Spec .Verify .SecretRef ; secretRef != nil {
1353
+ certSecretName := types.NamespacedName {
1354
+ Namespace : obj .Namespace ,
1355
+ Name : secretRef .Name ,
1356
+ }
1357
+
1358
+ var pubSecret corev1.Secret
1359
+ if err := r .Get (ctx , certSecretName , & pubSecret ); err != nil {
1360
+ return nil , err
1361
+ }
1362
+
1363
+ for k , data := range pubSecret .Data {
1364
+ // search for public keys in the secret
1365
+ if strings .HasSuffix (k , ".pub" ) {
1366
+ verifier , err := soci .NewCosignVerifier (ctx , append (defaultCosignOciOpts , soci .WithPublicKey (data ))... )
1367
+ if err != nil {
1368
+ return nil , err
1369
+ }
1370
+ verifiers = append (verifiers , verifier )
1371
+ }
1372
+ }
1373
+
1374
+ if len (verifiers ) == 0 {
1375
+ return nil , fmt .Errorf ("no public keys found in secret '%s'" , certSecretName )
1376
+ }
1377
+ return verifiers , nil
1378
+ }
1379
+
1380
+ // if no secret is provided, add a keyless verifier
1381
+ verifier , err := soci .NewCosignVerifier (ctx , defaultCosignOciOpts ... )
1382
+ if err != nil {
1383
+ return nil , err
1384
+ }
1385
+ verifiers = append (verifiers , verifier )
1386
+ return verifiers , nil
1387
+ default :
1388
+ return nil , fmt .Errorf ("unsupported verification provider: %s" , obj .Spec .Verify .Provider )
1389
+ }
1390
+ }
0 commit comments