@@ -18,8 +18,11 @@ package controllers
18
18
19
19
import (
20
20
"context"
21
+ "crypto/tls"
22
+ "crypto/x509"
21
23
"errors"
22
24
"fmt"
25
+ "net/http"
23
26
"os"
24
27
"sort"
25
28
"strings"
@@ -31,6 +34,7 @@ import (
31
34
"github.com/google/go-containerregistry/pkg/crane"
32
35
"github.com/google/go-containerregistry/pkg/name"
33
36
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
37
+ "github.com/google/go-containerregistry/pkg/v1/remote"
34
38
corev1 "k8s.io/api/core/v1"
35
39
"k8s.io/apimachinery/pkg/runtime"
36
40
"k8s.io/apimachinery/pkg/types"
@@ -61,6 +65,13 @@ import (
61
65
"github.com/fluxcd/source-controller/internal/util"
62
66
)
63
67
68
+ const (
69
+ ClientCert = "certFile"
70
+ ClientKey = "keyFile"
71
+ CACert = "caFile"
72
+ // CosignObjectRegex = "^.*\\.sig$"
73
+ )
74
+
64
75
// ociRepositoryReadyCondition contains the information required to summarize a
65
76
// v1beta2.OCIRepository Ready Condition.
66
77
var ociRepositoryReadyCondition = summarize.Conditions {
@@ -295,16 +306,24 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
295
306
return sreconcile .ResultEmpty , e
296
307
}
297
308
309
+ // Generates transport for remote operations
310
+ transport , err := r .transport (ctx , obj )
311
+ if err != nil {
312
+ e := serror .NewGeneric (err , sourcev1 .OCIOperationFailedReason )
313
+ conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
314
+ return sreconcile .ResultEmpty , e
315
+ }
316
+
298
317
// Determine which artifact revision to pull
299
- url , err := r .getArtifactURL (ctxTimeout , obj , keychain )
318
+ url , err := r .getArtifactURL (ctxTimeout , obj , keychain , transport )
300
319
if err != nil {
301
320
e := serror .NewGeneric (err , sourcev1 .OCIOperationFailedReason )
302
321
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
303
322
return sreconcile .ResultEmpty , e
304
323
}
305
324
306
325
// Pull artifact from the remote container registry
307
- img , err := crane .Pull (url , r .craneOptions (ctxTimeout , keychain )... )
326
+ img , err := crane .Pull (url , r .craneOptions (ctxTimeout , keychain , transport )... )
308
327
if err != nil {
309
328
e := serror .NewGeneric (err , sourcev1 .OCIOperationFailedReason )
310
329
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
@@ -382,7 +401,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
382
401
}
383
402
384
403
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
385
- func (r * OCIRepositoryReconciler ) getArtifactURL (ctx context.Context , obj * sourcev1.OCIRepository , keychain authn.Keychain ) (string , error ) {
404
+ func (r * OCIRepositoryReconciler ) getArtifactURL (ctx context.Context , obj * sourcev1.OCIRepository , keychain authn.Keychain , transport http. RoundTripper ) (string , error ) {
386
405
url , err := r .parseRepositoryURL (obj )
387
406
if err != nil {
388
407
return "" , err
@@ -394,7 +413,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
394
413
}
395
414
396
415
if obj .Spec .Reference .SemVer != "" {
397
- tag , err := r .getTagBySemver (ctx , url , obj .Spec .Reference .SemVer , keychain )
416
+ tag , err := r .getTagBySemver (ctx , url , obj .Spec .Reference .SemVer , keychain , transport )
398
417
if err != nil {
399
418
return "" , err
400
419
}
@@ -411,8 +430,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
411
430
412
431
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
413
432
// and returns the latest tag according to the semver expression.
414
- func (r * OCIRepositoryReconciler ) getTagBySemver (ctx context.Context , url , exp string , keychain authn.Keychain ) (string , error ) {
415
- tags , err := crane .ListTags (url , r .craneOptions (ctx , keychain )... )
433
+ func (r * OCIRepositoryReconciler ) getTagBySemver (ctx context.Context , url , exp string , keychain authn.Keychain , transport http. RoundTripper ) (string , error ) {
434
+ tags , err := crane .ListTags (url , r .craneOptions (ctx , keychain , transport )... )
416
435
if err != nil {
417
436
return "" , err
418
437
}
@@ -486,13 +505,62 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
486
505
return k8schain .NewFromPullSecrets (ctx , imagePullSecrets )
487
506
}
488
507
508
+ // transport clones the default transport from remote.
509
+ // If certSecretRef is configured in the resource configuration,
510
+ // returned transport will iclude client and/or CA certifactes
511
+ func (r * OCIRepositoryReconciler ) transport (ctx context.Context , obj * sourcev1.OCIRepository ) (http.RoundTripper , error ) {
512
+ if obj .Spec .CertSecretRef != nil {
513
+ var certSecret corev1.Secret
514
+ err := r .Get (ctx ,
515
+ types.NamespacedName {Namespace : obj .Namespace , Name : obj .Spec .CertSecretRef .Name },
516
+ & certSecret )
517
+
518
+ if err != nil {
519
+ r .eventLogf (ctx , obj , events .EventSeverityTrace , "secret %q not found" , obj .Spec .CertSecretRef .Name )
520
+ return nil , err
521
+ }
522
+
523
+ transport := remote .DefaultTransport .Clone ()
524
+ tlsConfig := transport .TLSClientConfig
525
+
526
+ if clientCert , ok := certSecret .Data [ClientCert ]; ok {
527
+ // parse and set client cert and secret
528
+ if clientKey , ok := certSecret .Data [ClientKey ]; ok {
529
+ cert , err := tls .X509KeyPair (clientCert , clientKey )
530
+ if err != nil {
531
+ return nil , err
532
+ }
533
+ tlsConfig .Certificates = append (tlsConfig .Certificates , cert )
534
+ } else {
535
+ return nil , fmt .Errorf ("client certificate found, but no key" )
536
+ }
537
+ }
538
+ if caCert , ok := certSecret .Data [CACert ]; ok {
539
+ syscerts , err := x509 .SystemCertPool ()
540
+ if err != nil {
541
+ return nil , err
542
+ }
543
+ syscerts .AppendCertsFromPEM (caCert )
544
+ tlsConfig .RootCAs = syscerts
545
+ }
546
+ return transport , nil
547
+ }
548
+ return nil , nil
549
+ }
550
+
489
551
// craneOptions sets the timeout and user agent for all operations against remote container registries.
490
- func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context , keychain authn.Keychain ) []crane.Option {
491
- return []crane.Option {
552
+ func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context , keychain authn.Keychain , transport http. RoundTripper ) []crane.Option {
553
+ options := []crane.Option {
492
554
crane .WithContext (ctx ),
493
555
crane .WithUserAgent ("flux/v2" ),
494
556
crane .WithAuthFromKeychain (keychain ),
495
557
}
558
+
559
+ if transport != nil {
560
+ options = append (options , crane .WithTransport (transport ))
561
+ }
562
+
563
+ return options
496
564
}
497
565
498
566
// reconcileStorage ensures the current state of the storage matches the
0 commit comments