Skip to content

Commit 9e157c2

Browse files
committed
OCIRepository client cert auth
Signed-off-by: Rashed Kamal <[email protected]>
1 parent ba5f535 commit 9e157c2

File tree

2 files changed

+363
-13
lines changed

2 files changed

+363
-13
lines changed

controllers/ocirepository_controller.go

Lines changed: 76 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,13 @@ import (
6165
"github.com/fluxcd/source-controller/internal/util"
6266
)
6367

68+
const (
69+
ClientCert = "certFile"
70+
ClientKey = "keyFile"
71+
CACert = "caFile"
72+
// CosignObjectRegex = "^.*\\.sig$"
73+
)
74+
6475
// ociRepositoryReadyCondition contains the information required to summarize a
6576
// v1beta2.OCIRepository Ready Condition.
6677
var ociRepositoryReadyCondition = summarize.Conditions{
@@ -295,16 +306,24 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
295306
return sreconcile.ResultEmpty, e
296307
}
297308

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+
298317
// Determine which artifact revision to pull
299-
url, err := r.getArtifactURL(ctxTimeout, obj, keychain)
318+
url, err := r.getArtifactURL(ctxTimeout, obj, keychain, transport)
300319
if err != nil {
301320
e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
302321
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
303322
return sreconcile.ResultEmpty, e
304323
}
305324

306325
// 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)...)
308327
if err != nil {
309328
e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
310329
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -382,7 +401,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
382401
}
383402

384403
// 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) {
386405
url, err := r.parseRepositoryURL(obj)
387406
if err != nil {
388407
return "", err
@@ -394,7 +413,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
394413
}
395414

396415
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)
398417
if err != nil {
399418
return "", err
400419
}
@@ -411,8 +430,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
411430

412431
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
413432
// 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)...)
416435
if err != nil {
417436
return "", err
418437
}
@@ -486,13 +505,62 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
486505
return k8schain.NewFromPullSecrets(ctx, imagePullSecrets)
487506
}
488507

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+
489551
// 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{
492554
crane.WithContext(ctx),
493555
crane.WithUserAgent("flux/v2"),
494556
crane.WithAuthFromKeychain(keychain),
495557
}
558+
559+
if transport != nil {
560+
options = append(options, crane.WithTransport(transport))
561+
}
562+
563+
return options
496564
}
497565

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

0 commit comments

Comments
 (0)