@@ -28,6 +28,7 @@ import (
2828 "strings"
2929 "time"
3030
31+ soci "github.com/fluxcd/source-controller/internal/oci"
3132 helmgetter "helm.sh/helm/v3/pkg/getter"
3233 helmreg "helm.sh/helm/v3/pkg/registry"
3334 corev1 "k8s.io/api/core/v1"
@@ -57,6 +58,7 @@ import (
5758 "github.com/fluxcd/pkg/runtime/predicates"
5859 "github.com/fluxcd/pkg/untar"
5960 "github.com/google/go-containerregistry/pkg/authn"
61+ "github.com/google/go-containerregistry/pkg/v1/remote"
6062
6163 sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
6264 "github.com/fluxcd/source-controller/internal/cache"
@@ -80,6 +82,7 @@ var helmChartReadyCondition = summarize.Conditions{
8082 sourcev1 .BuildFailedCondition ,
8183 sourcev1 .ArtifactOutdatedCondition ,
8284 sourcev1 .ArtifactInStorageCondition ,
85+ sourcev1 .SourceVerifiedCondition ,
8386 meta .ReadyCondition ,
8487 meta .ReconcilingCondition ,
8588 meta .StalledCondition ,
@@ -90,6 +93,7 @@ var helmChartReadyCondition = summarize.Conditions{
9093 sourcev1 .BuildFailedCondition ,
9194 sourcev1 .ArtifactOutdatedCondition ,
9295 sourcev1 .ArtifactInStorageCondition ,
96+ sourcev1 .SourceVerifiedCondition ,
9397 meta .StalledCondition ,
9498 meta .ReconcilingCondition ,
9599 },
@@ -564,17 +568,38 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
564568 }()
565569 }
566570
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+
567588 // Tell the chart repository to use the OCI client with the configured getter
568589 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 ))
570595 if err != nil {
571596 return chartRepoConfigErrorReturn (err , obj )
572597 }
573598 chartRepo = ociChartRepo
574599
575600 // If login options are configured, use them to login to the registry
576601 // The OCIGetter will later retrieve the stored credentials to pull the chart
577- if keychain != nil {
602+ if loginOpt != nil {
578603 err = ociChartRepo .Login (loginOpt )
579604 if err != nil {
580605 e := & serror.Event {
@@ -622,6 +647,17 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
622647 opts := chart.BuildOptions {
623648 ValuesFiles : obj .GetValuesFiles (),
624649 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 ),
625661 }
626662 if artifact := obj .GetArtifact (); artifact != nil {
627663 opts .CachedChart = r .Storage .LocalPath (* artifact )
@@ -1030,7 +1066,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10301066
10311067 // If login options are configured, use them to login to the registry
10321068 // The OCIGetter will later retrieve the stored credentials to pull the chart
1033- if keychain != nil {
1069+ if loginOpt != nil {
10341070 err = ociChartRepo .Login (loginOpt )
10351071 if err != nil {
10361072 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) {
12391275 if build .Complete () {
12401276 conditions .Delete (obj , sourcev1 .FetchFailedCondition )
12411277 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 )
12421283 }
12431284
12441285 if err != nil {
@@ -1251,7 +1292,7 @@ func observeChartBuild(obj *sourcev1.HelmChart, build *chart.Build, err error) {
12511292 }
12521293
12531294 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 :
12551296 conditions .Delete (obj , sourcev1 .FetchFailedCondition )
12561297 conditions .MarkTrue (obj , sourcev1 .BuildFailedCondition , buildErr .Reason .Reason , buildErr .Error ())
12571298 default :
@@ -1290,3 +1331,60 @@ func chartRepoConfigErrorReturn(err error, obj *sourcev1.HelmChart) (sreconcile.
12901331 return sreconcile .ResultEmpty , e
12911332 }
12921333}
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