Skip to content

Commit 85b8c39

Browse files
committed
fix: add registry client TLS connection idle timeout
1 parent 4513fe1 commit 85b8c39

File tree

3 files changed

+191
-11
lines changed

3 files changed

+191
-11
lines changed

internal/helm_client.go

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ package internal
1818

1919
import (
2020
"context"
21+
"crypto/tls"
22+
"crypto/x509"
2123
"fmt"
24+
"net/http"
2225
"net/url"
2326
"os"
2427
"path"
28+
"time"
2529

2630
"github.com/google/go-cmp/cmp"
2731
"github.com/pkg/errors"
@@ -255,21 +259,63 @@ func (c *HelmClient) InstallHelmRelease(ctx context.Context, restConfig *rest.Co
255259

256260
// newDefaultRegistryClient creates registry client object with default config which can be used to install/upgrade helm charts.
257261
func newDefaultRegistryClient(credentialsPath string, enableCache bool, caFilePath string, insecureSkipTLSVerify bool) (*registry.Client, error) {
258-
if caFilePath == "" && !insecureSkipTLSVerify {
259-
opts := []registry.ClientOption{
260-
registry.ClientOptDebug(true),
261-
registry.ClientOptEnableCache(enableCache),
262-
registry.ClientOptWriter(os.Stderr),
263-
}
264-
if credentialsPath != "" {
265-
// Create a new registry client with credentials
266-
opts = append(opts, registry.ClientOptCredentialsFile(credentialsPath))
262+
opts := []registry.ClientOption{
263+
registry.ClientOptDebug(true),
264+
registry.ClientOptEnableCache(enableCache),
265+
registry.ClientOptWriter(os.Stderr),
266+
}
267+
if credentialsPath != "" {
268+
// Create a new registry client with credentials
269+
opts = append(opts, registry.ClientOptCredentialsFile(credentialsPath))
270+
}
271+
272+
if caFilePath != "" || insecureSkipTLSVerify {
273+
tlsConf, err := newClientTLS(caFilePath, insecureSkipTLSVerify)
274+
if err != nil {
275+
return nil, fmt.Errorf("can't create TLS config for client: %s", err)
267276
}
277+
opts = append(opts, registry.ClientOptHTTPClient(&http.Client{
278+
Transport: &http.Transport{
279+
TLSClientConfig: tlsConf,
280+
Proxy: http.ProxyFromEnvironment,
281+
// This registry client is not reused and is discarded after a single reconciliation
282+
// loop. Limit how long can be the idle connection open. Otherwise its possible that
283+
// a registry server that keeps the connection open for a long time could result in
284+
// connections being openend in the controller for a long time.
285+
IdleConnTimeout: 1 * time.Second,
286+
},
287+
}),
288+
)
289+
}
290+
return registry.NewClient(opts...)
291+
}
268292

269-
return registry.NewClient(opts...)
293+
func newClientTLS(caFile string, insecureSkipTLSverify bool) (*tls.Config, error) {
294+
config := tls.Config{
295+
InsecureSkipVerify: insecureSkipTLSverify,
270296
}
271297

272-
return registry.NewRegistryClientWithTLS(os.Stderr, "", "", caFilePath, insecureSkipTLSVerify, credentialsPath, true)
298+
if caFile != "" {
299+
cp, err := certPoolFromFile(caFile)
300+
if err != nil {
301+
return nil, err
302+
}
303+
config.RootCAs = cp
304+
}
305+
306+
return &config, nil
307+
}
308+
309+
func certPoolFromFile(filename string) (*x509.CertPool, error) {
310+
b, err := os.ReadFile(filename)
311+
if err != nil {
312+
return nil, errors.Errorf("can't read CA file: %v", filename)
313+
}
314+
cp := x509.NewCertPool()
315+
if !cp.AppendCertsFromPEM(b) {
316+
return nil, errors.Errorf("failed to append certificates from file: %s", filename)
317+
}
318+
return cp, nil
273319
}
274320

275321
// getHelmChartAndRepoName returns chartName, repoURL as per the format requirred in helm install/upgrade config.

internal/helm_client_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internal
18+
19+
import (
20+
"os"
21+
"path/filepath"
22+
"testing"
23+
24+
. "github.com/onsi/gomega"
25+
"github.com/onsi/gomega/types"
26+
)
27+
28+
func TestNewDefaultRegistryClient(t *testing.T) {
29+
t.Parallel()
30+
31+
emptyFilepath := filepath.Join(t.TempDir(), "empty-file")
32+
NewWithT(t).Expect(os.Create(emptyFilepath)).Error().ShouldNot(HaveOccurred())
33+
34+
testCases := []struct {
35+
name string
36+
credentialsPath string
37+
enableCache bool
38+
caFilePath string
39+
insecureSkipTLSVerify bool
40+
assertErr types.GomegaMatcher
41+
assertClient types.GomegaMatcher
42+
}{
43+
{
44+
name: "default config",
45+
credentialsPath: "",
46+
enableCache: true,
47+
caFilePath: "",
48+
insecureSkipTLSVerify: false,
49+
assertErr: Not(HaveOccurred()),
50+
assertClient: Not(BeNil()),
51+
},
52+
{
53+
name: "skip tls verification",
54+
credentialsPath: "",
55+
enableCache: true,
56+
caFilePath: "",
57+
insecureSkipTLSVerify: true,
58+
assertErr: Not(HaveOccurred()),
59+
assertClient: Not(BeNil()),
60+
},
61+
{
62+
name: "invalid ca bundle path",
63+
credentialsPath: "",
64+
enableCache: true,
65+
caFilePath: filepath.Join(t.TempDir(), "non-existing"),
66+
insecureSkipTLSVerify: false,
67+
assertErr: MatchError(ContainSubstring("can't create TLS config")),
68+
assertClient: BeNil(),
69+
},
70+
{
71+
name: "empty ca bundle file",
72+
credentialsPath: "",
73+
enableCache: true,
74+
caFilePath: emptyFilepath,
75+
insecureSkipTLSVerify: false,
76+
assertErr: MatchError(ContainSubstring("failed to append certificates from file")),
77+
assertClient: BeNil(),
78+
},
79+
{
80+
name: "valid ca bundle",
81+
credentialsPath: "",
82+
enableCache: true,
83+
caFilePath: "testdata/ca-bundle.pem",
84+
insecureSkipTLSVerify: false,
85+
assertErr: Not(HaveOccurred()),
86+
assertClient: Not(BeNil()),
87+
},
88+
}
89+
90+
for _, tc := range testCases {
91+
t.Run(tc.name, func(t *testing.T) {
92+
g := NewWithT(t)
93+
client, err := newDefaultRegistryClient(
94+
tc.credentialsPath,
95+
tc.enableCache,
96+
tc.caFilePath,
97+
tc.insecureSkipTLSVerify,
98+
)
99+
g.Expect(err).To(tc.assertErr)
100+
g.Expect(client).To(tc.assertClient)
101+
})
102+
}
103+
}

internal/testdata/ca-bundle.pem

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3+
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4+
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5+
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6+
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7+
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8+
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10+
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11+
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12+
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13+
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14+
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15+
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16+
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17+
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18+
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19+
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20+
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21+
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22+
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23+
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24+
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25+
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26+
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27+
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28+
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29+
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30+
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)