@@ -20,6 +20,7 @@ import (
20
20
"context"
21
21
"crypto/tls"
22
22
"crypto/x509"
23
+ "encoding/base64"
23
24
"errors"
24
25
"fmt"
25
26
"net/http"
@@ -28,6 +29,8 @@ import (
28
29
"strings"
29
30
"time"
30
31
32
+ "github.com/fluxcd/source-controller/internal/verify"
33
+
31
34
"github.com/Masterminds/semver/v3"
32
35
"github.com/google/go-containerregistry/pkg/authn"
33
36
"github.com/google/go-containerregistry/pkg/authn/k8schain"
@@ -362,6 +365,18 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
362
365
return sreconcile .ResultEmpty , e
363
366
}
364
367
368
+ // Verify the image
369
+ if obj .Spec .Verify != nil {
370
+ if _ , err := r .verify (ctx , obj , url ); err != nil {
371
+ e := serror .NewGeneric (
372
+ fmt .Errorf ("failed to verify '%s' using provider '%s': %w" , url , obj .Spec .Verify .Provider , err ),
373
+ sourcev1 .SourceVerifiedFailedReason ,
374
+ )
375
+ conditions .MarkTrue (obj , sourcev1 .SourceVerifiedCondition , e .Reason , e .Err .Error ())
376
+ return sreconcile .ResultEmpty , e
377
+ }
378
+ }
379
+
365
380
// Pull artifact from the remote container registry
366
381
img , err := crane .Pull (url , options ... )
367
382
if err != nil {
@@ -658,7 +673,6 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
658
673
tlsConfig .RootCAs = syscerts
659
674
}
660
675
return transport , nil
661
-
662
676
}
663
677
664
678
// oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
@@ -705,7 +719,8 @@ func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Opti
705
719
// The hostname of any URL in the Status of the object are updated, to ensure
706
720
// they match the Storage server hostname of current runtime.
707
721
func (r * OCIRepositoryReconciler ) reconcileStorage (ctx context.Context ,
708
- obj * sourcev1.OCIRepository , _ * sourcev1.Artifact , _ string ) (sreconcile.Result , error ) {
722
+ obj * sourcev1.OCIRepository , _ * sourcev1.Artifact , _ string ,
723
+ ) (sreconcile.Result , error ) {
709
724
// Garbage collect previous advertised artifact(s) from storage
710
725
_ = r .garbageCollect (ctx , obj )
711
726
@@ -741,7 +756,8 @@ func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context,
741
756
// On a successful archive, the Artifact in the Status of the object is set,
742
757
// and the symlink in the Storage is updated to its path.
743
758
func (r * OCIRepositoryReconciler ) reconcileArtifact (ctx context.Context ,
744
- obj * sourcev1.OCIRepository , metadata * sourcev1.Artifact , dir string ) (sreconcile.Result , error ) {
759
+ obj * sourcev1.OCIRepository , metadata * sourcev1.Artifact , dir string ,
760
+ ) (sreconcile.Result , error ) {
745
761
// Calculate revision
746
762
revision := metadata .Revision
747
763
@@ -885,7 +901,8 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc
885
901
// that this is a simple log. While the debug log contains complete details
886
902
// about the event.
887
903
func (r * OCIRepositoryReconciler ) eventLogf (ctx context.Context ,
888
- obj runtime.Object , eventType string , reason string , messageFmt string , args ... interface {}) {
904
+ obj runtime.Object , eventType , reason , messageFmt string , args ... interface {},
905
+ ) {
889
906
msg := fmt .Sprintf (messageFmt , args ... )
890
907
// Log and emit event.
891
908
if eventType == corev1 .EventTypeWarning {
@@ -898,7 +915,8 @@ func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,
898
915
899
916
// notify emits notification related to the reconciliation.
900
917
func (r * OCIRepositoryReconciler ) notify (ctx context.Context ,
901
- oldObj , newObj * sourcev1.OCIRepository , res sreconcile.Result , resErr error ) {
918
+ oldObj , newObj * sourcev1.OCIRepository , res sreconcile.Result , resErr error ,
919
+ ) {
902
920
// Notify successful reconciliation for new artifact and recovery from any
903
921
// failure.
904
922
if resErr == nil && res == sreconcile .ResultSuccess && newObj .Status .Artifact != nil {
@@ -942,3 +960,53 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context,
942
960
}
943
961
}
944
962
}
963
+
964
+ // notify emits notification related to the reconciliation.
965
+ func (r * OCIRepositoryReconciler ) verify (ctx context.Context , obj * sourcev1.OCIRepository , url string ) (bool , error ) {
966
+ // get the public keys from the given secret
967
+ certSecretName := types.NamespacedName {
968
+ Namespace : obj .Namespace ,
969
+ Name : obj .Spec .Verify .SecretRef .Name ,
970
+ }
971
+
972
+ var pubSecret corev1.Secret
973
+ if err := r .Get (ctx , certSecretName , & pubSecret ); err != nil {
974
+ return false , err
975
+ }
976
+
977
+ ref , err := name .ParseReference (url )
978
+ if err != nil {
979
+ return false , err
980
+ }
981
+
982
+ // traverse all public keys and try to verify the signature
983
+ // this is brute-force approach, but it is ok for now
984
+ verified := false
985
+ for _ , data := range pubSecret .Data {
986
+ pubRaw , err := base64 .StdEncoding .DecodeString (string (data ))
987
+ if err != nil {
988
+ return false , err
989
+ }
990
+
991
+ opts := []verify.Options {
992
+ verify .WithPublicKey (pubRaw ),
993
+ }
994
+
995
+ verifier , err := verify .New (obj .Spec .Verify .Provider , opts ... )
996
+ if err != nil {
997
+ return false , err
998
+ }
999
+
1000
+ signatures , _ , err := verifier .VerifyImageSignatures (ctx , ref )
1001
+ if err != nil {
1002
+ return false , err
1003
+ }
1004
+
1005
+ if len (signatures ) > 0 {
1006
+ verified = true
1007
+ break
1008
+ }
1009
+ }
1010
+
1011
+ return verified , nil
1012
+ }
0 commit comments