@@ -28,6 +28,8 @@ import (
28
28
"strings"
29
29
"time"
30
30
31
+ soci "github.com/fluxcd/source-controller/internal/oci"
32
+
31
33
"github.com/Masterminds/semver/v3"
32
34
"github.com/google/go-containerregistry/pkg/authn"
33
35
"github.com/google/go-containerregistry/pkg/authn/k8schain"
@@ -39,6 +41,7 @@ import (
39
41
"k8s.io/apimachinery/pkg/runtime"
40
42
"k8s.io/apimachinery/pkg/types"
41
43
"k8s.io/apimachinery/pkg/util/sets"
44
+ "k8s.io/apimachinery/pkg/util/uuid"
42
45
kuberecorder "k8s.io/client-go/tools/record"
43
46
44
47
ctrl "sigs.k8s.io/controller-runtime"
@@ -159,7 +162,9 @@ func (r *OCIRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, o
159
162
160
163
func (r * OCIRepositoryReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (result ctrl.Result , retErr error ) {
161
164
start := time .Now ()
162
- log := ctrl .LoggerFrom (ctx )
165
+ log := ctrl .LoggerFrom (ctx ).
166
+ // Sets a reconcile ID to correlate logs from all suboperations.
167
+ WithValues ("reconcileID" , uuid .NewUUID ())
163
168
164
169
// logger will be associated to the new context that is
165
170
// returned from ctrl.LoggerInto.
@@ -298,7 +303,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
298
303
ctxTimeout , cancel := context .WithTimeout (ctx , obj .Spec .Timeout .Duration )
299
304
defer cancel ()
300
305
301
- options := r .craneOptions (ctxTimeout , obj . Spec . Insecure )
306
+ options := r .craneOptions (ctxTimeout )
302
307
303
308
// Generate the registry credential keychain either from static credentials or using cloud OIDC
304
309
keychain , err := r .keychain (ctx , obj )
@@ -412,6 +417,19 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
412
417
413
418
// Extract the content of the first artifact layer
414
419
if ! obj .GetArtifact ().HasRevision (revision ) {
420
+ provider := obj .Spec .Verify .Provider
421
+ err := r .verifyOCISourceSignature (ctx , obj , url )
422
+ if err != nil {
423
+ e := serror .NewGeneric (
424
+ fmt .Errorf ("failed to verify '%s' using provider '%s': %w" , url , provider , err ),
425
+ sourcev1 .OCISourceSignatureVerifyFailedReason ,
426
+ )
427
+ conditions .MarkFalse (obj , sourcev1 .SourceVerifiedCondition , e .Reason , e .Err .Error ())
428
+ return sreconcile .ResultEmpty , e
429
+ } else {
430
+ conditions .MarkTrue (obj , sourcev1 .SourceVerifiedCondition , "OCI Image %s with digest %s verified." , url , imgDigest )
431
+ }
432
+
415
433
layers , err := img .Layers ()
416
434
if err != nil {
417
435
e := serror .NewGeneric (
@@ -488,6 +506,82 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
488
506
return sreconcile .ResultSuccess , nil
489
507
}
490
508
509
+ // verifyOCISourceSignature verifies the authenticity of the given image reference url. First, it tries to keyful approach
510
+ // by looking at whether the given secret exists. Then, if it does not exist, it pushes a keyless approach for verification.
511
+ func (r * OCIRepositoryReconciler ) verifyOCISourceSignature (ctx context.Context , obj * sourcev1.OCIRepository , url string ) error {
512
+ // Verify the image
513
+ if obj .Spec .Verify != nil {
514
+ provider := obj .Spec .Verify .Provider
515
+ switch provider {
516
+ case "cosign" :
517
+ // get the public keys from the given secret
518
+ secretRef := obj .Spec .Verify .SecretRef
519
+
520
+ // Generate the registry credential keychain either from static credentials or using cloud OIDC
521
+ keychain , err := r .keychain (ctx , obj )
522
+ if err != nil {
523
+ return err
524
+ }
525
+ authnKeychain := soci .WithAuthnKeychain (keychain )
526
+
527
+ ref , err := name .ParseReference (url )
528
+ if err != nil {
529
+ return err
530
+ }
531
+
532
+ if secretRef != nil {
533
+ certSecretName := types.NamespacedName {
534
+ Namespace : obj .Namespace ,
535
+ Name : secretRef .Name ,
536
+ }
537
+
538
+ var pubSecret corev1.Secret
539
+ if err := r .Get (ctx , certSecretName , & pubSecret ); err != nil {
540
+ return err
541
+ }
542
+
543
+ // traverse all public keys and try to verify the signature
544
+ // this is brute-force approach, but it is ok for now
545
+ for k , data := range pubSecret .Data {
546
+ // search for public keys in the secret
547
+ if strings .HasSuffix (k , ".pub" ) {
548
+ verifier , err := soci .New (soci .WithPublicKey (data ), authnKeychain )
549
+ if err != nil {
550
+ return err
551
+ }
552
+
553
+ signatures , _ , err := verifier .VerifyImageSignatures (ctx , ref )
554
+ if err != nil {
555
+ ctrl .LoggerFrom (ctx ).Error (err , "failed to verify image %s signature with key %s" , url , k )
556
+ continue
557
+ }
558
+
559
+ if signatures != nil {
560
+ return nil
561
+ }
562
+ }
563
+ }
564
+ } else {
565
+ verifier , err := soci .New (authnKeychain )
566
+ if err != nil {
567
+ return err
568
+ }
569
+
570
+ signatures , _ , err := verifier .VerifyImageSignatures (ctx , ref )
571
+ if err != nil {
572
+ return err
573
+ }
574
+
575
+ if len (signatures ) > 0 {
576
+ return nil
577
+ }
578
+ }
579
+ return nil
580
+ }
581
+ }
582
+ return nil
583
+ }
584
+
491
585
// parseRepositoryURL validates and extracts the repository URL.
492
586
func (r * OCIRepositoryReconciler ) parseRepositoryURL (obj * sourcev1.OCIRepository ) (string , error ) {
493
587
if ! strings .HasPrefix (obj .Spec .URL , sourcev1 .OCIRepositoryPrefix ) {
@@ -655,7 +749,6 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
655
749
tlsConfig .RootCAs = syscerts
656
750
}
657
751
return transport , nil
658
-
659
752
}
660
753
661
754
// oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
@@ -681,16 +774,11 @@ func (r *OCIRepositoryReconciler) oidcAuth(ctx context.Context, obj *sourcev1.OC
681
774
682
775
// craneOptions sets the auth headers, timeout and user agent
683
776
// for all operations against remote container registries.
684
- func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context , insecure bool ) []crane.Option {
777
+ func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context ) []crane.Option {
685
778
options := []crane.Option {
686
779
crane .WithContext (ctx ),
687
780
crane .WithUserAgent (oci .UserAgent ),
688
781
}
689
-
690
- if insecure {
691
- options = append (options , crane .Insecure )
692
- }
693
-
694
782
return options
695
783
}
696
784
@@ -887,7 +975,8 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc
887
975
// that this is a simple log. While the debug log contains complete details
888
976
// about the event.
889
977
func (r * OCIRepositoryReconciler ) eventLogf (ctx context.Context ,
890
- obj runtime.Object , eventType string , reason string , messageFmt string , args ... interface {}) {
978
+ obj runtime.Object , eventType , reason , messageFmt string , args ... interface {},
979
+ ) {
891
980
msg := fmt .Sprintf (messageFmt , args ... )
892
981
// Log and emit event.
893
982
if eventType == corev1 .EventTypeWarning {
0 commit comments