Skip to content

Commit 933ec5f

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 38cff76 commit 933ec5f

13 files changed

+573
-180
lines changed

docs/spec/v1beta2/helmrepositories.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,6 @@ a deprecation warning will be logged.
459459

460460
### Cert secret reference
461461

462-
**Note:** TLS authentication is not yet supported by OCI Helm repositories.
463-
464462
`.spec.certSecretRef.name` is an optional field to specify a secret containing TLS
465463
certificate data. The secret can contain the following keys:
466464

internal/controller/helmchart_controller.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
512512
if err != nil {
513513
return chartRepoConfigErrorReturn(err, obj)
514514
}
515-
clientOpts, err := getter.GetClientOpts(ctxTimeout, r.Client, repo, normalizedURL)
515+
516+
clientOpts, certsTmpDir, err := getter.GetClientOpts(ctxTimeout, r.Client, repo, normalizedURL)
516517
if err != nil && !errors.Is(err, getter.ErrDeprecatedTLSConfig) {
517518
e := &serror.Event{
518519
Err: err,
@@ -521,6 +522,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
521522
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
522523
return sreconcile.ResultEmpty, e
523524
}
525+
defer os.RemoveAll(certsTmpDir)
526+
524527
getterOpts := clientOpts.GetterOpts
525528

526529
// Initialize the chart repository
@@ -536,7 +539,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
536539
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
537540
// TODO@souleb: remove this once the registry move to Oras v2
538541
// or rework to enable reusing credentials to avoid the unneccessary handshake operations
539-
registryClient, credentialsFile, err := r.RegistryClientGenerator(clientOpts.RegLoginOpt != nil)
542+
registryClient, credentialsFile, err := r.RegistryClientGenerator(clientOpts.TlsConfig, clientOpts.MustLoginToRegistry())
540543
if err != nil {
541544
e := &serror.Event{
542545
Err: fmt.Errorf("failed to construct Helm client: %w", err),
@@ -585,8 +588,8 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
585588

586589
// If login options are configured, use them to login to the registry
587590
// The OCIGetter will later retrieve the stored credentials to pull the chart
588-
if clientOpts.RegLoginOpt != nil {
589-
err = ociChartRepo.Login(clientOpts.RegLoginOpt)
591+
if clientOpts.MustLoginToRegistry() {
592+
err = ociChartRepo.Login(clientOpts.RegLoginOpts...)
590593
if err != nil {
591594
e := &serror.Event{
592595
Err: fmt.Errorf("failed to login to OCI registry: %w", err),
@@ -983,15 +986,17 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
983986
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
984987
defer cancel()
985988

986-
clientOpts, err := getter.GetClientOpts(ctxTimeout, r.Client, obj, normalizedURL)
989+
clientOpts, certsTmpDir, err := getter.GetClientOpts(ctxTimeout, r.Client, obj, normalizedURL)
987990
if err != nil && !errors.Is(err, getter.ErrDeprecatedTLSConfig) {
988991
return nil, err
989992
}
993+
defer os.RemoveAll(certsTmpDir)
994+
990995
getterOpts := clientOpts.GetterOpts
991996

992997
var chartRepo repository.Downloader
993998
if helmreg.IsOCI(normalizedURL) {
994-
registryClient, credentialsFile, err := r.RegistryClientGenerator(clientOpts.RegLoginOpt != nil)
999+
registryClient, credentialsFile, err := r.RegistryClientGenerator(clientOpts.TlsConfig, clientOpts.MustLoginToRegistry())
9951000
if err != nil {
9961001
return nil, fmt.Errorf("failed to create registry client: %w", err)
9971002
}
@@ -1016,8 +1021,8 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10161021

10171022
// If login options are configured, use them to login to the registry
10181023
// The OCIGetter will later retrieve the stored credentials to pull the chart
1019-
if clientOpts.RegLoginOpt != nil {
1020-
err = ociChartRepo.Login(clientOpts.RegLoginOpt)
1024+
if clientOpts.MustLoginToRegistry() {
1025+
err = ociChartRepo.Login(clientOpts.RegLoginOpts...)
10211026
if err != nil {
10221027
errs = append(errs, fmt.Errorf("failed to login to OCI chart repository: %w", err))
10231028
// clean up the credentialsFile

internal/controller/helmchart_controller_test.go

Lines changed: 125 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
11091109
g.Expect(err).NotTo(HaveOccurred())
11101110

11111111
// Upload the test chart
1112-
metadata, err := loadTestChartToOCI(chartData, chartPath, testRegistryServer)
1112+
metadata, err := loadTestChartToOCI(chartData, testRegistryServer, "", "", "")
11131113
g.Expect(err).NotTo(HaveOccurred())
11141114

11151115
storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords)
@@ -2244,53 +2244,74 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
22442244
url string
22452245
registryOpts registryOptions
22462246
secretOpts secretOptions
2247+
secret *corev1.Secret
2248+
certsecret *corev1.Secret
2249+
insecure bool
22472250
provider string
22482251
providerImg string
22492252
want sreconcile.Result
22502253
wantErr bool
22512254
assertConditions []metav1.Condition
22522255
}{
22532256
{
2254-
name: "HTTP without basic auth",
2255-
want: sreconcile.ResultSuccess,
2257+
name: "HTTP without basic auth",
2258+
want: sreconcile.ResultSuccess,
2259+
insecure: true,
22562260
assertConditions: []metav1.Condition{
22572261
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
22582262
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
22592263
},
22602264
},
22612265
{
2262-
name: "HTTP with basic auth secret",
2263-
want: sreconcile.ResultSuccess,
2266+
name: "HTTP with basic auth secret",
2267+
want: sreconcile.ResultSuccess,
2268+
insecure: true,
22642269
registryOpts: registryOptions{
22652270
withBasicAuth: true,
22662271
},
22672272
secretOpts: secretOptions{
22682273
username: testRegistryUsername,
22692274
password: testRegistryPassword,
22702275
},
2276+
secret: &corev1.Secret{
2277+
ObjectMeta: metav1.ObjectMeta{
2278+
Name: "auth-secretref",
2279+
},
2280+
Type: corev1.SecretTypeDockerConfigJson,
2281+
Data: map[string][]byte{},
2282+
},
22712283
assertConditions: []metav1.Condition{
22722284
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
22732285
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
22742286
},
22752287
},
22762288
{
2277-
name: "HTTP registry - basic auth with invalid secret",
2278-
want: sreconcile.ResultEmpty,
2279-
wantErr: true,
2289+
name: "HTTP registry - basic auth with invalid secret",
2290+
want: sreconcile.ResultEmpty,
2291+
wantErr: true,
2292+
insecure: true,
22802293
registryOpts: registryOptions{
22812294
withBasicAuth: true,
22822295
},
22832296
secretOpts: secretOptions{
22842297
username: "wrong-pass",
22852298
password: "wrong-pass",
22862299
},
2300+
secret: &corev1.Secret{
2301+
ObjectMeta: metav1.ObjectMeta{
2302+
Name: "auth-secretref",
2303+
},
2304+
Type: corev1.SecretTypeDockerConfigJson,
2305+
Data: map[string][]byte{},
2306+
},
22872307
assertConditions: []metav1.Condition{
22882308
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to login to OCI registry"),
22892309
},
22902310
},
22912311
{
22922312
name: "with contextual login provider",
22932313
wantErr: true,
2314+
insecure: true,
22942315
provider: "aws",
22952316
providerImg: "oci://123456789000.dkr.ecr.us-east-2.amazonaws.com/test",
22962317
assertConditions: []metav1.Condition{
@@ -2303,16 +2324,87 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
23032324
registryOpts: registryOptions{
23042325
withBasicAuth: true,
23052326
},
2327+
insecure: true,
23062328
secretOpts: secretOptions{
23072329
username: testRegistryUsername,
23082330
password: testRegistryPassword,
23092331
},
2332+
secret: &corev1.Secret{
2333+
ObjectMeta: metav1.ObjectMeta{
2334+
Name: "auth-secretref",
2335+
},
2336+
Type: corev1.SecretTypeDockerConfigJson,
2337+
Data: map[string][]byte{},
2338+
},
23102339
provider: "azure",
23112340
assertConditions: []metav1.Condition{
23122341
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
23132342
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
23142343
},
23152344
},
2345+
{
2346+
name: "HTTPS With invalid CA cert",
2347+
wantErr: true,
2348+
registryOpts: registryOptions{
2349+
withTLS: true,
2350+
withClientCertAuth: true,
2351+
},
2352+
secretOpts: secretOptions{
2353+
username: testRegistryUsername,
2354+
password: testRegistryPassword,
2355+
},
2356+
secret: &corev1.Secret{
2357+
ObjectMeta: metav1.ObjectMeta{
2358+
Name: "auth-secretref",
2359+
},
2360+
Type: corev1.SecretTypeDockerConfigJson,
2361+
Data: map[string][]byte{},
2362+
},
2363+
certsecret: &corev1.Secret{
2364+
ObjectMeta: metav1.ObjectMeta{
2365+
Name: "certs-secretref",
2366+
},
2367+
Data: map[string][]byte{
2368+
"caFile": []byte("invalid caFile"),
2369+
},
2370+
},
2371+
assertConditions: []metav1.Condition{
2372+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to construct Helm client's TLS config: cannot append certificate into certificate pool: invalid caFile"),
2373+
},
2374+
},
2375+
{
2376+
name: "HTTPS With CA cert",
2377+
want: sreconcile.ResultSuccess,
2378+
registryOpts: registryOptions{
2379+
withTLS: true,
2380+
withClientCertAuth: true,
2381+
},
2382+
secretOpts: secretOptions{
2383+
username: testRegistryUsername,
2384+
password: testRegistryPassword,
2385+
},
2386+
secret: &corev1.Secret{
2387+
ObjectMeta: metav1.ObjectMeta{
2388+
Name: "auth-secretref",
2389+
},
2390+
Type: corev1.SecretTypeDockerConfigJson,
2391+
Data: map[string][]byte{},
2392+
},
2393+
certsecret: &corev1.Secret{
2394+
ObjectMeta: metav1.ObjectMeta{
2395+
Name: "certs-secretref",
2396+
},
2397+
Data: map[string][]byte{
2398+
"caFile": tlsCA,
2399+
"certFile": clientPublicKey,
2400+
"keyFile": clientPrivateKey,
2401+
},
2402+
},
2403+
assertConditions: []metav1.Condition{
2404+
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
2405+
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"),
2406+
},
2407+
},
23162408
}
23172409

23182410
for _, tt := range tests {
@@ -2325,7 +2417,9 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
23252417

23262418
workspaceDir := t.TempDir()
23272419

2328-
tt.registryOpts.disableDNSMocking = true
2420+
if tt.insecure {
2421+
tt.registryOpts.disableDNSMocking = true
2422+
}
23292423
server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts)
23302424
g.Expect(err).NotTo(HaveOccurred())
23312425
t.Cleanup(func() {
@@ -2337,7 +2431,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
23372431
g.Expect(err).ToNot(HaveOccurred())
23382432

23392433
// Upload the test chart
2340-
metadata, err := loadTestChartToOCI(chartData, chartPath, server)
2434+
metadata, err := loadTestChartToOCI(chartData, server, "testdata/certs/client.pem", "testdata/certs/client-key.pem", "testdata/certs/ca.pem")
23412435
g.Expect(err).ToNot(HaveOccurred())
23422436

23432437
repo := &helmv1.HelmRepository{
@@ -2364,25 +2458,26 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
23642458
}
23652459

23662460
if tt.secretOpts.username != "" && tt.secretOpts.password != "" {
2367-
secret := &corev1.Secret{
2368-
ObjectMeta: metav1.ObjectMeta{
2369-
Name: "auth-secretref",
2370-
},
2371-
Type: corev1.SecretTypeDockerConfigJson,
2372-
Data: map[string][]byte{
2373-
".dockerconfigjson": []byte(fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`,
2374-
server.registryHost, tt.secretOpts.username, tt.secretOpts.password)),
2375-
},
2376-
}
2461+
tt.secret.Data[".dockerconfigjson"] = []byte(fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`,
2462+
server.registryHost, tt.secretOpts.username, tt.secretOpts.password))
2463+
}
23772464

2465+
if tt.secret != nil {
23782466
repo.Spec.SecretRef = &meta.LocalObjectReference{
2379-
Name: secret.Name,
2467+
Name: tt.secret.Name,
23802468
}
2381-
clientBuilder.WithObjects(secret, repo)
2382-
} else {
2383-
clientBuilder.WithObjects(repo)
2469+
clientBuilder.WithObjects(tt.secret)
2470+
}
2471+
2472+
if tt.certsecret != nil {
2473+
repo.Spec.CertSecretRef = &meta.LocalObjectReference{
2474+
Name: tt.certsecret.Name,
2475+
}
2476+
clientBuilder.WithObjects(tt.certsecret)
23842477
}
23852478

2479+
clientBuilder.WithObjects(repo)
2480+
23862481
obj := &helmv1.HelmChart{
23872482
ObjectMeta: metav1.ObjectMeta{
23882483
GenerateName: "auth-strategy-",
@@ -2456,7 +2551,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T
24562551
g.Expect(err).ToNot(HaveOccurred())
24572552

24582553
// Upload the test chart
2459-
metadata, err := loadTestChartToOCI(chartData, chartPath, server)
2554+
metadata, err := loadTestChartToOCI(chartData, server, "", "", "")
24602555
g.Expect(err).NotTo(HaveOccurred())
24612556

24622557
storage, err := NewStorage(tmpDir, "example.com", retentionTTL, retentionRecords)
@@ -2687,30 +2782,24 @@ func extractChartMeta(chartData []byte) (*hchart.Metadata, error) {
26872782
return ch.Metadata, nil
26882783
}
26892784

2690-
func loadTestChartToOCI(chartData []byte, chartPath string, server *registryClientTestServer) (*hchart.Metadata, error) {
2785+
func loadTestChartToOCI(chartData []byte, server *registryClientTestServer, certFile, keyFile, cafile string) (*hchart.Metadata, error) {
26912786
// Login to the registry
26922787
err := server.registryClient.Login(server.registryHost,
26932788
helmreg.LoginOptBasicAuth(testRegistryUsername, testRegistryPassword),
2694-
helmreg.LoginOptInsecure(true))
2695-
if err != nil {
2696-
return nil, err
2697-
}
2698-
2699-
// Load a test chart
2700-
chartData, err = os.ReadFile(chartPath)
2789+
helmreg.LoginOptTLSClientConfig(certFile, keyFile, cafile))
27012790
if err != nil {
2702-
return nil, err
2791+
return nil, fmt.Errorf("failed to login to OCI registry: %w", err)
27032792
}
27042793
metadata, err := extractChartMeta(chartData)
27052794
if err != nil {
2706-
return nil, err
2795+
return nil, fmt.Errorf("failed to extract chart metadata: %w", err)
27072796
}
27082797

27092798
// Upload the test chart
27102799
ref := fmt.Sprintf("%s/testrepo/%s:%s", server.registryHost, metadata.Name, metadata.Version)
27112800
_, err = server.registryClient.Push(chartData, ref)
27122801
if err != nil {
2713-
return nil, err
2802+
return nil, fmt.Errorf("failed to push chart: %w", err)
27142803
}
27152804

27162805
return metadata, nil

internal/controller/helmrepository_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patc
399399
return sreconcile.ResultEmpty, e
400400
}
401401

402-
clientOpts, err := getter.GetClientOpts(ctx, r.Client, obj, normalizedURL)
402+
clientOpts, _, err := getter.GetClientOpts(ctx, r.Client, obj, normalizedURL)
403403
if err != nil {
404404
if errors.Is(err, getter.ErrDeprecatedTLSConfig) {
405405
ctrl.LoggerFrom(ctx).

0 commit comments

Comments
 (0)