Skip to content

Commit 3e3008f

Browse files
committed
Add support for custom certificate and skip-tls-verify in helm OCI
If implemented user will be able to provide their own custom start and bypass tls verification when interacting with OCI registries over https to pull helmCharts. Signed-off-by: Soule BA <[email protected]>
1 parent 75a30f9 commit 3e3008f

11 files changed

+106
-44
lines changed

api/v1/zz_generated.deepcopy.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/helmrepository_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ type HelmRepositorySpec struct {
8080
// +optional
8181
Timeout *metav1.Duration `json:"timeout,omitempty"`
8282

83+
// InsecureSkipTLSverify skips the validation of the TLS certificate of the
84+
// OCI registry endpoint.
85+
// +optional
86+
InsecureSkipTLSverify bool `json:"insecureSkipTLSverify,omitempty"`
87+
8388
// Suspend tells the controller to suspend the reconciliation of this
8489
// HelmRepository.
8590
// +optional

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/controller/helmchart_controller.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,10 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
576576
}
577577
}
578578

579+
if repo.Spec.InsecureSkipTLSverify {
580+
tlsConfig.InsecureSkipVerify = true
581+
}
582+
579583
loginOpt, err := makeLoginOption(authenticator, keychain, normalizedURL)
580584
if err != nil {
581585
e := &serror.Event{
@@ -599,7 +603,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
599603
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
600604
// TODO@souleb: remove this once the registry move to Oras v2
601605
// or rework to enable reusing credentials to avoid the unneccessary handshake operations
602-
registryClient, credentialsFile, err := r.RegistryClientGenerator(loginOpt != nil)
606+
registryClient, credentialsFile, err := r.RegistryClientGenerator(tlsConfig, loginOpt != nil)
603607
if err != nil {
604608
e := &serror.Event{
605609
Err: fmt.Errorf("failed to construct Helm client: %w", err),
@@ -1086,14 +1090,18 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10861090
}
10871091
}
10881092

1093+
if obj.Spec.InsecureSkipTLSverify {
1094+
tlsConfig.InsecureSkipVerify = true
1095+
}
1096+
10891097
loginOpt, err := makeLoginOption(authenticator, keychain, normalizedURL)
10901098
if err != nil {
10911099
return nil, err
10921100
}
10931101

10941102
var chartRepo repository.Downloader
10951103
if helmreg.IsOCI(normalizedURL) {
1096-
registryClient, credentialsFile, err := r.RegistryClientGenerator(loginOpt != nil)
1104+
registryClient, credentialsFile, err := r.RegistryClientGenerator(tlsConfig, loginOpt != nil)
10971105
if err != nil {
10981106
return nil, fmt.Errorf("failed to create registry client for HelmRepository '%s': %w", obj.Name, err)
10991107
}

internal/controller/helmrepository_controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patc
441441
}
442442
}
443443

444+
if obj.Spec.InsecureSkipTLSverify {
445+
tlsConfig.InsecureSkipVerify = true
446+
}
447+
444448
// Construct Helm chart repository with options and download index
445449
newChartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", r.Getters, tlsConfig, clientOpts...)
446450
if err != nil {

internal/controller/helmrepository_controller_oci.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ package controller
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"errors"
2223
"fmt"
2324
"net/url"
2425
"os"
2526
"time"
2627

2728
"github.com/google/go-containerregistry/pkg/authn"
28-
helmgetter "helm.sh/helm/v3/pkg/getter"
2929
helmreg "helm.sh/helm/v3/pkg/registry"
3030
corev1 "k8s.io/api/core/v1"
3131
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -51,6 +51,7 @@ import (
5151

5252
sourcev1 "github.com/fluxcd/source-controller/api/v1"
5353
helmv1 "github.com/fluxcd/source-controller/api/v1beta2"
54+
"github.com/fluxcd/source-controller/internal/helm/getter"
5455
"github.com/fluxcd/source-controller/internal/helm/registry"
5556
"github.com/fluxcd/source-controller/internal/helm/repository"
5657
"github.com/fluxcd/source-controller/internal/object"
@@ -78,7 +79,7 @@ type HelmRepositoryOCIReconciler struct {
7879
client.Client
7980
kuberecorder.EventRecorder
8081
helper.Metrics
81-
Getters helmgetter.Providers
82+
8283
ControllerName string
8384
RegistryClientGenerator RegistryClientGeneratorFunc
8485

@@ -94,7 +95,7 @@ type HelmRepositoryOCIReconciler struct {
9495
// and an optional file name.
9596
// The file is used to store the registry client credentials.
9697
// The caller is responsible for deleting the file.
97-
type RegistryClientGeneratorFunc func(isLogin bool) (*helmreg.Client, string, error)
98+
type RegistryClientGeneratorFunc func(tlsConfig *tls.Config, isLogin bool) (*helmreg.Client, string, error)
9899

99100
func (r *HelmRepositoryOCIReconciler) SetupWithManager(mgr ctrl.Manager) error {
100101
return r.SetupWithManagerAndOptions(mgr, HelmRepositoryReconcilerOptions{})
@@ -307,11 +308,29 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, sp *patch.S
307308
var (
308309
authenticator authn.Authenticator
309310
keychain authn.Keychain
311+
tlsConfig *tls.Config
310312
err error
311313
)
312314
// Configure any authentication related options.
313315
if obj.Spec.SecretRef != nil {
314-
keychain, err = authFromSecret(ctx, r.Client, obj)
316+
// Attempt to retrieve secret.
317+
name := types.NamespacedName{
318+
Namespace: obj.GetNamespace(),
319+
Name: obj.Spec.SecretRef.Name,
320+
}
321+
var secret corev1.Secret
322+
if err := r.Client.Get(ctx, name, &secret); err != nil {
323+
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.AuthenticationFailedReason, err.Error())
324+
result, retErr = ctrl.Result{}, err
325+
return
326+
}
327+
keychain, err = authFromSecret(ctx, r.Client, obj.Spec.URL, secret)
328+
if err != nil {
329+
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.AuthenticationFailedReason, err.Error())
330+
result, retErr = ctrl.Result{}, err
331+
return
332+
}
333+
tlsConfig, err = getter.TLSClientConfigFromSecret(secret, obj.Spec.URL)
315334
if err != nil {
316335
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.AuthenticationFailedReason, err.Error())
317336
result, retErr = ctrl.Result{}, err
@@ -330,6 +349,10 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, sp *patch.S
330349
}
331350
}
332351

352+
if obj.Spec.InsecureSkipTLSverify {
353+
tlsConfig.InsecureSkipVerify = true
354+
}
355+
333356
loginOpt, err := makeLoginOption(authenticator, keychain, obj.Spec.URL)
334357
if err != nil {
335358
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.AuthenticationFailedReason, err.Error())
@@ -338,7 +361,7 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, sp *patch.S
338361
}
339362

340363
// Create registry client and login if needed.
341-
registryClient, file, err := r.RegistryClientGenerator(loginOpt != nil)
364+
registryClient, file, err := r.RegistryClientGenerator(tlsConfig, loginOpt != nil)
342365
if err != nil {
343366
e := fmt.Errorf("failed to create registry client: %w", err)
344367
conditions.MarkFalse(obj, meta.ReadyCondition, meta.FailedReason, e.Error())
@@ -409,20 +432,10 @@ func (r *HelmRepositoryOCIReconciler) eventLogf(ctx context.Context, obj runtime
409432
}
410433

411434
// authFromSecret returns an authn.Keychain for the given HelmRepository.
412-
// If the HelmRepository does not specify a secretRef, an anonymous keychain is returned.
413-
func authFromSecret(ctx context.Context, client client.Client, obj *helmv1.HelmRepository) (authn.Keychain, error) {
414-
// Attempt to retrieve secret.
415-
name := types.NamespacedName{
416-
Namespace: obj.GetNamespace(),
417-
Name: obj.Spec.SecretRef.Name,
418-
}
419-
var secret corev1.Secret
420-
if err := client.Get(ctx, name, &secret); err != nil {
421-
return nil, fmt.Errorf("failed to get secret '%s': %w", name.String(), err)
422-
}
423-
435+
// If the provided secret does not contain credentials, an anonymous keychain is returned.
436+
func authFromSecret(ctx context.Context, client client.Client, registryURL string, secret corev1.Secret) (authn.Keychain, error) {
424437
// Construct login options.
425-
keychain, err := registry.LoginOptionFromSecret(obj.Spec.URL, secret)
438+
keychain, err := registry.LoginOptionFromSecret(registryURL, secret)
426439
if err != nil {
427440
return nil, fmt.Errorf("failed to configure Helm client with secret data: %w", err)
428441
}

internal/controller/helmrepository_controller_oci_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
299299
r := &HelmRepositoryOCIReconciler{
300300
Client: clientBuilder.Build(),
301301
EventRecorder: record.NewFakeRecorder(32),
302-
Getters: testGetters,
303302
RegistryClientGenerator: registry.ClientGenerator,
304303
patchOptions: getPatchOptions(helmRepositoryOCIOwnedConditions, "sc"),
305304
}

internal/controller/suite_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ func TestMain(m *testing.M) {
294294
Client: testEnv,
295295
EventRecorder: record.NewFakeRecorder(32),
296296
Metrics: testMetricsH,
297-
Getters: testGetters,
298297
RegistryClientGenerator: registry.ClientGenerator,
299298
}).SetupWithManagerAndOptions(testEnv, HelmRepositoryReconcilerOptions{
300299
RateLimiter: controller.GetDefaultRateLimiter(),

internal/helm/registry/client.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ limitations under the License.
1717
package registry
1818

1919
import (
20+
"crypto/tls"
2021
"io"
22+
"net/http"
2123
"os"
2224

2325
"helm.sh/helm/v3/pkg/registry"
@@ -27,7 +29,7 @@ import (
2729
// ClientGenerator generates a registry client and a temporary credential file.
2830
// The client is meant to be used for a single reconciliation.
2931
// The file is meant to be used for a single reconciliation and deleted after.
30-
func ClientGenerator(isLogin bool) (*registry.Client, string, error) {
32+
func ClientGenerator(tlsConfig *tls.Config, isLogin bool) (*registry.Client, string, error) {
3133
if isLogin {
3234
// create a temporary file to store the credentials
3335
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
@@ -37,7 +39,7 @@ func ClientGenerator(isLogin bool) (*registry.Client, string, error) {
3739
}
3840

3941
var errs []error
40-
rClient, err := registry.NewClient(registry.ClientOptWriter(io.Discard), registry.ClientOptCredentialsFile(credentialsFile.Name()))
42+
rClient, err := newClient(credentialsFile.Name(), tlsConfig)
4143
if err != nil {
4244
errs = append(errs, err)
4345
// attempt to delete the temporary file
@@ -52,9 +54,42 @@ func ClientGenerator(isLogin bool) (*registry.Client, string, error) {
5254
return rClient, credentialsFile.Name(), nil
5355
}
5456

55-
rClient, err := registry.NewClient(registry.ClientOptWriter(io.Discard))
57+
rClient, err := newClient("", tlsConfig)
5658
if err != nil {
5759
return nil, "", err
5860
}
5961
return rClient, "", nil
6062
}
63+
64+
func newClient(credentialsFile string, tlsConfig *tls.Config) (*registry.Client, error) {
65+
if tlsConfig != nil {
66+
return newClientWithTLS(credentialsFile, tlsConfig)
67+
}
68+
return newDefaultClient(credentialsFile)
69+
}
70+
71+
func newDefaultClient(credentialsFile string) (*registry.Client, error) {
72+
if credentialsFile == "" {
73+
return registry.NewClient(registry.ClientOptWriter(io.Discard))
74+
}
75+
return registry.NewClient(registry.ClientOptWriter(io.Discard),
76+
registry.ClientOptCredentialsFile(credentialsFile))
77+
}
78+
79+
func newClientWithTLS(credentialsFile string, tlsConfig *tls.Config) (*registry.Client, error) {
80+
if credentialsFile == "" {
81+
return registry.NewClient(registry.ClientOptWriter(io.Discard),
82+
registry.ClientOptHTTPClient(&http.Client{
83+
Transport: &http.Transport{
84+
TLSClientConfig: tlsConfig,
85+
},
86+
}))
87+
}
88+
return registry.NewClient(registry.ClientOptWriter(io.Discard),
89+
registry.ClientOptCredentialsFile(credentialsFile),
90+
registry.ClientOptHTTPClient(&http.Client{
91+
Transport: &http.Transport{
92+
TLSClientConfig: tlsConfig,
93+
},
94+
}))
95+
}

0 commit comments

Comments
 (0)