Skip to content

Commit 6c29768

Browse files
authored
Merge pull request #821 from rashedkvm/oci-tls
OCIRepository client cert auth
2 parents ba5f535 + 0518bba commit 6c29768

File tree

2 files changed

+362
-13
lines changed

2 files changed

+362
-13
lines changed

controllers/ocirepository_controller.go

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ package controllers
1818

1919
import (
2020
"context"
21+
"crypto/tls"
22+
"crypto/x509"
2123
"errors"
2224
"fmt"
25+
"net/http"
2326
"os"
2427
"sort"
2528
"strings"
@@ -31,6 +34,7 @@ import (
3134
"github.com/google/go-containerregistry/pkg/crane"
3235
"github.com/google/go-containerregistry/pkg/name"
3336
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
37+
"github.com/google/go-containerregistry/pkg/v1/remote"
3438
corev1 "k8s.io/api/core/v1"
3539
"k8s.io/apimachinery/pkg/runtime"
3640
"k8s.io/apimachinery/pkg/types"
@@ -61,6 +65,12 @@ import (
6165
"github.com/fluxcd/source-controller/internal/util"
6266
)
6367

68+
const (
69+
ClientCert = "certFile"
70+
ClientKey = "keyFile"
71+
CACert = "caFile"
72+
)
73+
6474
// ociRepositoryReadyCondition contains the information required to summarize a
6575
// v1beta2.OCIRepository Ready Condition.
6676
var ociRepositoryReadyCondition = summarize.Conditions{
@@ -295,16 +305,24 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
295305
return sreconcile.ResultEmpty, e
296306
}
297307

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+
298316
// Determine which artifact revision to pull
299-
url, err := r.getArtifactURL(ctxTimeout, obj, keychain)
317+
url, err := r.getArtifactURL(ctxTimeout, obj, keychain, transport)
300318
if err != nil {
301319
e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
302320
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
303321
return sreconcile.ResultEmpty, e
304322
}
305323

306324
// 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)...)
308326
if err != nil {
309327
e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
310328
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -382,7 +400,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
382400
}
383401

384402
// 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) {
386404
url, err := r.parseRepositoryURL(obj)
387405
if err != nil {
388406
return "", err
@@ -394,7 +412,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
394412
}
395413

396414
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)
398416
if err != nil {
399417
return "", err
400418
}
@@ -411,8 +429,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
411429

412430
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
413431
// 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)...)
416434
if err != nil {
417435
return "", err
418436
}
@@ -486,13 +504,62 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
486504
return k8schain.NewFromPullSecrets(ctx, imagePullSecrets)
487505
}
488506

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+
489550
// 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{
492553
crane.WithContext(ctx),
493554
crane.WithUserAgent("flux/v2"),
494555
crane.WithAuthFromKeychain(keychain),
495556
}
557+
558+
if transport != nil {
559+
options = append(options, crane.WithTransport(transport))
560+
}
561+
562+
return options
496563
}
497564

498565
// reconcileStorage ensures the current state of the storage matches the

0 commit comments

Comments
 (0)