@@ -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,12 @@ 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
+ )
73
+
64
74
// ociRepositoryReadyCondition contains the information required to summarize a
65
75
// v1beta2.OCIRepository Ready Condition.
66
76
var ociRepositoryReadyCondition = summarize.Conditions {
@@ -295,16 +305,24 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
295
305
return sreconcile .ResultEmpty , e
296
306
}
297
307
308
+ // Generates transport for remote operations
309
+ transport , err := r .transport (ctx , obj )
310
+ if err != nil {
311
+ e := serror .NewGeneric (err , sourcev1 .OCIOperationFailedReason )
312
+ conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
313
+ return sreconcile .ResultEmpty , e
314
+ }
315
+
298
316
// Determine which artifact revision to pull
299
- url , err := r .getArtifactURL (ctxTimeout , obj , keychain )
317
+ url , err := r .getArtifactURL (ctxTimeout , obj , keychain , transport )
300
318
if err != nil {
301
319
e := serror .NewGeneric (err , sourcev1 .OCIOperationFailedReason )
302
320
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
303
321
return sreconcile .ResultEmpty , e
304
322
}
305
323
306
324
// Pull artifact from the remote container registry
307
- img , err := crane .Pull (url , r .craneOptions (ctxTimeout , keychain )... )
325
+ img , err := crane .Pull (url , r .craneOptions (ctxTimeout , keychain , transport )... )
308
326
if err != nil {
309
327
e := serror .NewGeneric (err , sourcev1 .OCIOperationFailedReason )
310
328
conditions .MarkTrue (obj , sourcev1 .FetchFailedCondition , e .Reason , e .Error ())
@@ -382,7 +400,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
382
400
}
383
401
384
402
// 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 ) {
403
+ func (r * OCIRepositoryReconciler ) getArtifactURL (ctx context.Context , obj * sourcev1.OCIRepository , keychain authn.Keychain , transport http. RoundTripper ) (string , error ) {
386
404
url , err := r .parseRepositoryURL (obj )
387
405
if err != nil {
388
406
return "" , err
@@ -394,7 +412,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
394
412
}
395
413
396
414
if obj .Spec .Reference .SemVer != "" {
397
- tag , err := r .getTagBySemver (ctx , url , obj .Spec .Reference .SemVer , keychain )
415
+ tag , err := r .getTagBySemver (ctx , url , obj .Spec .Reference .SemVer , keychain , transport )
398
416
if err != nil {
399
417
return "" , err
400
418
}
@@ -411,8 +429,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
411
429
412
430
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
413
431
// 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 )... )
432
+ func (r * OCIRepositoryReconciler ) getTagBySemver (ctx context.Context , url , exp string , keychain authn.Keychain , transport http. RoundTripper ) (string , error ) {
433
+ tags , err := crane .ListTags (url , r .craneOptions (ctx , keychain , transport )... )
416
434
if err != nil {
417
435
return "" , err
418
436
}
@@ -486,13 +504,62 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
486
504
return k8schain .NewFromPullSecrets (ctx , imagePullSecrets )
487
505
}
488
506
507
+ // transport clones the default transport from remote.
508
+ // If certSecretRef is configured in the resource configuration,
509
+ // returned transport will iclude client and/or CA certifactes
510
+ func (r * OCIRepositoryReconciler ) transport (ctx context.Context , obj * sourcev1.OCIRepository ) (http.RoundTripper , error ) {
511
+ if obj .Spec .CertSecretRef != nil {
512
+ var certSecret corev1.Secret
513
+ err := r .Get (ctx ,
514
+ types.NamespacedName {Namespace : obj .Namespace , Name : obj .Spec .CertSecretRef .Name },
515
+ & certSecret )
516
+
517
+ if err != nil {
518
+ r .eventLogf (ctx , obj , events .EventSeverityTrace , "secret %q not found" , obj .Spec .CertSecretRef .Name )
519
+ return nil , err
520
+ }
521
+
522
+ transport := remote .DefaultTransport .Clone ()
523
+ tlsConfig := transport .TLSClientConfig
524
+
525
+ if clientCert , ok := certSecret .Data [ClientCert ]; ok {
526
+ // parse and set client cert and secret
527
+ if clientKey , ok := certSecret .Data [ClientKey ]; ok {
528
+ cert , err := tls .X509KeyPair (clientCert , clientKey )
529
+ if err != nil {
530
+ return nil , err
531
+ }
532
+ tlsConfig .Certificates = append (tlsConfig .Certificates , cert )
533
+ } else {
534
+ return nil , fmt .Errorf ("client certificate found, but no key" )
535
+ }
536
+ }
537
+ if caCert , ok := certSecret .Data [CACert ]; ok {
538
+ syscerts , err := x509 .SystemCertPool ()
539
+ if err != nil {
540
+ return nil , err
541
+ }
542
+ syscerts .AppendCertsFromPEM (caCert )
543
+ tlsConfig .RootCAs = syscerts
544
+ }
545
+ return transport , nil
546
+ }
547
+ return nil , nil
548
+ }
549
+
489
550
// 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 {
551
+ func (r * OCIRepositoryReconciler ) craneOptions (ctx context.Context , keychain authn.Keychain , transport http. RoundTripper ) []crane.Option {
552
+ options := []crane.Option {
492
553
crane .WithContext (ctx ),
493
554
crane .WithUserAgent ("flux/v2" ),
494
555
crane .WithAuthFromKeychain (keychain ),
495
556
}
557
+
558
+ if transport != nil {
559
+ options = append (options , crane .WithTransport (transport ))
560
+ }
561
+
562
+ return options
496
563
}
497
564
498
565
// reconcileStorage ensures the current state of the storage matches the
0 commit comments