Skip to content

Commit d9d789f

Browse files
author
Paulo Gomes
committed
Reuse transport for helm chart download
Reuses the same transport across different helm chart downloads, whilst resetting the tlsconfig to avoid cross-contamination. Crypto material is now only processed in-memory and does not touch the disk. Signed-off-by: Paulo Gomes <[email protected]>
1 parent b28669e commit d9d789f

File tree

11 files changed

+287
-115
lines changed

11 files changed

+287
-115
lines changed

controllers/helmchart_controller.go

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controllers
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"errors"
2223
"fmt"
2324
"net/url"
@@ -368,6 +369,7 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, obj *sourcev1
368369

369370
func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *sourcev1.HelmChart,
370371
repo *sourcev1.HelmRepository, b *chart.Build) (sreconcile.Result, error) {
372+
var tlsConfig *tls.Config
371373

372374
// Construct the Getter options from the HelmRepository data
373375
clientOpts := []helmgetter.Option{
@@ -386,34 +388,33 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
386388
return sreconcile.ResultEmpty, e
387389
}
388390

389-
// Create temporary working directory for credentials
390-
authDir, err := util.TempDirForObj("", obj)
391+
// Build client options from secret
392+
opts, err := getter.ClientOptionsFromSecret(*secret)
391393
if err != nil {
392394
e := &serror.Event{
393-
Err: fmt.Errorf("failed to create temporary working directory: %w", err),
394-
Reason: sourcev1.StorageOperationFailedReason,
395+
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),
396+
Reason: sourcev1.AuthenticationFailedReason,
395397
}
396-
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.StorageOperationFailedReason, e.Err.Error())
398+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, e.Err.Error())
399+
// Requeue as content of secret might change
397400
return sreconcile.ResultEmpty, e
398401
}
399-
defer os.RemoveAll(authDir)
402+
clientOpts = append(clientOpts, opts...)
400403

401-
// Build client options from secret
402-
opts, err := getter.ClientOptionsFromSecret(authDir, *secret)
404+
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, repo.Spec.URL)
403405
if err != nil {
404406
e := &serror.Event{
405-
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),
407+
Err: fmt.Errorf("failed to create tls client config with secret data: %w", err),
406408
Reason: sourcev1.AuthenticationFailedReason,
407409
}
408410
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, e.Err.Error())
409411
// Requeue as content of secret might change
410412
return sreconcile.ResultEmpty, e
411413
}
412-
clientOpts = append(clientOpts, opts...)
413414
}
414415

415416
// Initialize the chart repository
416-
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, clientOpts)
417+
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, tlsConfig, clientOpts)
417418
if err != nil {
418419
// Any error requires a change in generation,
419420
// which we should be informed about by the watcher
@@ -523,15 +524,8 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
523524
}
524525

525526
// Setup dependency manager
526-
authDir := filepath.Join(tmpDir, "creds")
527-
if err = os.Mkdir(authDir, 0700); err != nil {
528-
return sreconcile.ResultEmpty, &serror.Event{
529-
Err: fmt.Errorf("failed to create temporary directory for dependency credentials: %w", err),
530-
Reason: meta.FailedReason,
531-
}
532-
}
533527
dm := chart.NewDependencyManager(
534-
chart.WithRepositoryCallback(r.namespacedChartRepositoryCallback(ctx, authDir, obj.GetNamespace())),
528+
chart.WithRepositoryCallback(r.namespacedChartRepositoryCallback(ctx, obj.GetNamespace())),
535529
)
536530
defer dm.Clear()
537531

@@ -747,11 +741,11 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
747741
}
748742

749743
// namespacedChartRepositoryCallback returns a chart.GetChartRepositoryCallback scoped to the given namespace.
750-
// Credentials for retrieved v1beta1.HelmRepository objects are stored in the given directory.
751744
// The returned callback returns a repository.ChartRepository configured with the retrieved v1beta1.HelmRepository,
752745
// or a shim with defaults if no object could be found.
753-
func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, dir, namespace string) chart.GetChartRepositoryCallback {
746+
func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, namespace string) chart.GetChartRepositoryCallback {
754747
return func(url string) (*repository.ChartRepository, error) {
748+
var tlsConfig *tls.Config
755749
repo, err := r.resolveDependencyRepository(ctx, url, namespace)
756750
if err != nil {
757751
// Return Kubernetes client errors, but ignore others
@@ -774,13 +768,19 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
774768
if err != nil {
775769
return nil, err
776770
}
777-
opts, err := getter.ClientOptionsFromSecret(dir, *secret)
771+
opts, err := getter.ClientOptionsFromSecret(*secret)
778772
if err != nil {
779773
return nil, err
780774
}
781775
clientOpts = append(clientOpts, opts...)
776+
777+
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, repo.Spec.URL)
778+
if err != nil {
779+
return nil, fmt.Errorf("failed to create tls client config for HelmRepository '%s': %w", repo.Name, err)
780+
}
782781
}
783-
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, "", r.Getters, clientOpts)
782+
783+
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, "", r.Getters, tlsConfig, clientOpts)
784784
if err != nil {
785785
return nil, err
786786
}

controllers/helmrepository_controller.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controllers
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"errors"
2223
"fmt"
2324
"net/url"
@@ -260,6 +261,8 @@ func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, obj *so
260261
// If the download is successful, the given artifact pointer is set to a new artifact with the available metadata, and
261262
// the index pointer is set to the newly downloaded index.
262263
func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) {
264+
var tlsConfig *tls.Config
265+
263266
// Configure Helm client to access repository
264267
clientOpts := []helmgetter.Option{
265268
helmgetter.WithTimeout(obj.Spec.Timeout.Duration),
@@ -284,18 +287,8 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, obj *sou
284287
return sreconcile.ResultEmpty, e
285288
}
286289

287-
// Get client options from secret
288-
tmpDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s-auth-", obj.Name, obj.Namespace))
289-
if err != nil {
290-
return sreconcile.ResultEmpty, &serror.Event{
291-
Err: fmt.Errorf("failed to create temporary directory for credentials: %w", err),
292-
Reason: sourcev1.StorageOperationFailedReason,
293-
}
294-
}
295-
defer os.RemoveAll(tmpDir)
296-
297290
// Construct actual options
298-
opts, err := getter.ClientOptionsFromSecret(tmpDir, secret)
291+
opts, err := getter.ClientOptionsFromSecret(secret)
299292
if err != nil {
300293
e := &serror.Event{
301294
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),
@@ -306,10 +299,21 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, obj *sou
306299
return sreconcile.ResultEmpty, e
307300
}
308301
clientOpts = append(clientOpts, opts...)
302+
303+
tlsConfig, err = getter.TLSClientConfigFromSecret(secret, obj.Spec.URL)
304+
if err != nil {
305+
e := &serror.Event{
306+
Err: fmt.Errorf("failed to create tls client config with secret data: %w", err),
307+
Reason: sourcev1.AuthenticationFailedReason,
308+
}
309+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, e.Err.Error())
310+
// Requeue as content of secret might change
311+
return sreconcile.ResultEmpty, e
312+
}
309313
}
310314

311315
// Construct Helm chart repository with options and download index
312-
newChartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", r.Getters, clientOpts)
316+
newChartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", r.Getters, tlsConfig, clientOpts)
313317
if err != nil {
314318
switch err.(type) {
315319
case *url.Error:

controllers/helmrepository_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
366366
},
367367
wantErr: true,
368368
assertConditions: []metav1.Condition{
369-
*conditions.TrueCondition(sourcev1.FetchFailedCondition, meta.FailedReason, "can't create TLS config for client: failed to append certificates from file"),
369+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to create tls client config with secret data: cannot append certificate into certificate pool: invalid caFile"),
370370
},
371371
},
372372
{
@@ -602,7 +602,7 @@ func TestHelmRepositoryReconciler_reconcileArtifact(t *testing.T) {
602602
g.Expect(err).ToNot(HaveOccurred())
603603
g.Expect(cacheFile.Close()).ToNot(HaveOccurred())
604604

605-
chartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", testGetters, nil)
605+
chartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", testGetters, nil, nil)
606606
g.Expect(err).ToNot(HaveOccurred())
607607
chartRepo.CachePath = cachePath
608608

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ require (
4040
k8s.io/api v0.23.3
4141
k8s.io/apimachinery v0.23.3
4242
k8s.io/client-go v0.23.3
43+
k8s.io/helm v2.17.0+incompatible
4344
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
4445
sigs.k8s.io/cli-utils v0.28.0
4546
sigs.k8s.io/controller-runtime v0.11.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,8 @@ k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
17591759
k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
17601760
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
17611761
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
1762+
k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao=
1763+
k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
17621764
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
17631765
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
17641766
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=

internal/helm/getter/getter.go

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ limitations under the License.
1717
package getter
1818

1919
import (
20+
"crypto/tls"
21+
"crypto/x509"
2022
"fmt"
21-
"os"
2223

2324
"helm.sh/helm/v3/pkg/getter"
2425
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/helm/pkg/urlutil"
2527
)
2628

2729
// ClientOptionsFromSecret constructs a getter.Option slice for the given secret.
2830
// It returns the slice, or an error.
29-
func ClientOptionsFromSecret(dir string, secret corev1.Secret) ([]getter.Option, error) {
31+
func ClientOptionsFromSecret(secret corev1.Secret) ([]getter.Option, error) {
3032
var opts []getter.Option
3133
basicAuth, err := BasicAuthFromSecret(secret)
3234
if err != nil {
@@ -35,13 +37,6 @@ func ClientOptionsFromSecret(dir string, secret corev1.Secret) ([]getter.Option,
3537
if basicAuth != nil {
3638
opts = append(opts, basicAuth)
3739
}
38-
tlsClientConfig, err := TLSClientConfigFromSecret(dir, secret)
39-
if err != nil {
40-
return opts, err
41-
}
42-
if tlsClientConfig != nil {
43-
opts = append(opts, tlsClientConfig)
44-
}
4540
return opts, nil
4641
}
4742

@@ -62,13 +57,11 @@ func BasicAuthFromSecret(secret corev1.Secret) (getter.Option, error) {
6257
}
6358

6459
// TLSClientConfigFromSecret attempts to construct a TLS client config
65-
// getter.Option for the given v1.Secret, placing the required TLS config
66-
// related files in the given directory. It returns the getter.Option, or
67-
// an error.
60+
// for the given v1.Secret. It returns the TLS client config or an error.
6861
//
6962
// Secrets with no certFile, keyFile, AND caFile are ignored, if only a
7063
// certBytes OR keyBytes is defined it returns an error.
71-
func TLSClientConfigFromSecret(dir string, secret corev1.Secret) (getter.Option, error) {
64+
func TLSClientConfigFromSecret(secret corev1.Secret, url string) (*tls.Config, error) {
7265
certBytes, keyBytes, caBytes := secret.Data["certFile"], secret.Data["keyFile"], secret.Data["caFile"]
7366
switch {
7467
case len(certBytes)+len(keyBytes)+len(caBytes) == 0:
@@ -78,49 +71,31 @@ func TLSClientConfigFromSecret(dir string, secret corev1.Secret) (getter.Option,
7871
secret.Name)
7972
}
8073

81-
var certPath, keyPath, caPath string
74+
tlsConf := &tls.Config{}
8275
if len(certBytes) > 0 && len(keyBytes) > 0 {
83-
certFile, err := os.CreateTemp(dir, "cert-*.crt")
84-
if err != nil {
85-
return nil, err
86-
}
87-
if _, err = certFile.Write(certBytes); err != nil {
88-
_ = certFile.Close()
89-
return nil, err
90-
}
91-
if err = certFile.Close(); err != nil {
92-
return nil, err
93-
}
94-
certPath = certFile.Name()
95-
96-
keyFile, err := os.CreateTemp(dir, "key-*.crt")
76+
cert, err := tls.X509KeyPair(certBytes, keyBytes)
9777
if err != nil {
9878
return nil, err
9979
}
100-
if _, err = keyFile.Write(keyBytes); err != nil {
101-
_ = keyFile.Close()
102-
return nil, err
103-
}
104-
if err = keyFile.Close(); err != nil {
105-
return nil, err
106-
}
107-
keyPath = keyFile.Name()
80+
tlsConf.Certificates = append(tlsConf.Certificates, cert)
10881
}
10982

11083
if len(caBytes) > 0 {
111-
caFile, err := os.CreateTemp(dir, "ca-*.pem")
112-
if err != nil {
113-
return nil, err
84+
cp := x509.NewCertPool()
85+
if !cp.AppendCertsFromPEM(caBytes) {
86+
return nil, fmt.Errorf("cannot append certificate into certificate pool: invalid caFile")
11487
}
115-
if _, err = caFile.Write(caBytes); err != nil {
116-
_ = caFile.Close()
117-
return nil, err
118-
}
119-
if err = caFile.Close(); err != nil {
120-
return nil, err
121-
}
122-
caPath = caFile.Name()
88+
89+
tlsConf.RootCAs = cp
90+
}
91+
92+
tlsConf.BuildNameToCertificate()
93+
94+
sni, err := urlutil.ExtractHostname(url)
95+
if err != nil {
96+
return nil, err
12397
}
98+
tlsConf.ServerName = sni
12499

125-
return getter.WithTLSClientConfig(certPath, keyPath, caPath), nil
100+
return tlsConf, nil
126101
}

0 commit comments

Comments
 (0)