Skip to content

Commit 1296423

Browse files
author
Max Jonas Werner
committed
dockerconfigjson for OCI registry authentication
`loginOptionFromSecret` now derives username/password from a docker config stored in Secrets of type "kubernetes.io/dockerconfigjson". Signed-off-by: Max Jonas Werner <[email protected]>
1 parent a23567c commit 1296423

4 files changed

+73
-4
lines changed

controllers/helmchart_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
492492
}
493493

494494
// Build registryClient options from secret
495-
logOpt, err := loginOptionFromSecret(*secret)
495+
logOpt, err := loginOptionFromSecret(repo.Spec.URL, *secret)
496496
if err != nil {
497497
e := &serror.Event{
498498
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),

controllers/helmchart_controller_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controllers
1919
import (
2020
"bytes"
2121
"context"
22+
"encoding/base64"
2223
"errors"
2324
"fmt"
2425
"io"
@@ -825,6 +826,35 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
825826
assertFunc func(g *WithT, obj *sourcev1.HelmChart, build chart.Build)
826827
cleanFunc func(g *WithT, build *chart.Build)
827828
}{
829+
{
830+
name: "Reconciles chart build with docker repository credentials",
831+
secret: &corev1.Secret{
832+
ObjectMeta: metav1.ObjectMeta{
833+
Name: "auth",
834+
},
835+
Type: corev1.SecretTypeDockerConfigJson,
836+
Data: map[string][]byte{
837+
".dockerconfigjson": []byte(`{"auths":{"` +
838+
testRegistryserver.DockerRegistryHost + `":{"` +
839+
`auth":"` + base64.StdEncoding.EncodeToString([]byte(testUsername+":"+testPassword)) + `"}}}`),
840+
},
841+
},
842+
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
843+
obj.Spec.Chart = metadata.Name
844+
obj.Spec.Version = metadata.Version
845+
repository.Spec.SecretRef = &meta.LocalObjectReference{Name: "auth"}
846+
},
847+
want: sreconcile.ResultSuccess,
848+
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
849+
g.Expect(build.Name).To(Equal(metadata.Name))
850+
g.Expect(build.Version).To(Equal(metadata.Version))
851+
g.Expect(build.Path).ToNot(BeEmpty())
852+
g.Expect(build.Path).To(BeARegularFile())
853+
},
854+
cleanFunc: func(g *WithT, build *chart.Build) {
855+
g.Expect(os.Remove(build.Path)).To(Succeed())
856+
},
857+
},
828858
{
829859
name: "Reconciles chart build with repository credentials",
830860
secret: &corev1.Secret{

controllers/helmrepository_controller_oci.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"fmt"
23+
"net/url"
2224
"os"
2325
"strings"
2426
"time"
2527

28+
"github.com/docker/cli/cli/config"
2629
"github.com/fluxcd/pkg/apis/meta"
2730
"github.com/fluxcd/pkg/runtime/conditions"
2831
helper "github.com/fluxcd/pkg/runtime/controller"
@@ -273,7 +276,7 @@ func (r *HelmRepositoryOCIReconciler) reconcileSource(ctx context.Context, obj *
273276
}
274277

275278
// Construct actual options
276-
logOpt, err := loginOptionFromSecret(secret)
279+
logOpt, err := loginOptionFromSecret(obj.Spec.URL, secret)
277280
if err != nil {
278281
e := &serror.Event{
279282
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),
@@ -352,8 +355,30 @@ func (r *HelmRepositoryOCIReconciler) validateSource(ctx context.Context, obj *s
352355
return sreconcile.ResultSuccess, nil
353356
}
354357

355-
func loginOptionFromSecret(secret corev1.Secret) (registry.LoginOption, error) {
356-
username, password := string(secret.Data["username"]), string(secret.Data["password"])
358+
// loginOptionFromSecret derives authentication data from a Secret to login to an OCI registry. This Secret
359+
// may either hold "username" and "password" fields or be of the corev1.SecretTypeDockerConfigJson type and hold
360+
// a corev1.DockerConfigJsonKey field with a complete Docker configuration. If both, "username" and "password" are
361+
// empty, a nil LoginOption and a nil error will be returned.
362+
func loginOptionFromSecret(registryURL string, secret corev1.Secret) (registry.LoginOption, error) {
363+
var username, password string
364+
if secret.Type == corev1.SecretTypeDockerConfigJson {
365+
dockerCfg, err := config.LoadFromReader(bytes.NewReader(secret.Data[corev1.DockerConfigJsonKey]))
366+
if err != nil {
367+
return nil, fmt.Errorf("unable to load Docker config: %w", err)
368+
}
369+
parsedURL, err := url.Parse(registryURL)
370+
if err != nil {
371+
return nil, fmt.Errorf("unable to parse registry URL: %w", err)
372+
}
373+
authConfig, err := dockerCfg.GetAuthConfig(parsedURL.Host)
374+
if err != nil {
375+
return nil, fmt.Errorf("unable to get authentication data from Secret: %w", err)
376+
}
377+
username = authConfig.Username
378+
password = authConfig.Password
379+
} else {
380+
username, password = string(secret.Data["username"]), string(secret.Data["password"])
381+
}
357382
switch {
358383
case username == "" && password == "":
359384
return nil, nil

controllers/helmrepository_controller_oci_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20+
"encoding/base64"
2021
"fmt"
2122
"testing"
2223

@@ -36,6 +37,7 @@ import (
3637
func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
3738
tests := []struct {
3839
name string
40+
secretType corev1.SecretType
3941
secretData map[string][]byte
4042
}{
4143
{
@@ -49,6 +51,15 @@ func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
4951
name: "no auth data",
5052
secretData: nil,
5153
},
54+
{
55+
name: "dockerconfigjson Secret",
56+
secretType: corev1.SecretTypeDockerConfigJson,
57+
secretData: map[string][]byte{
58+
".dockerconfigjson": []byte(`{"auths":{"` +
59+
testRegistryserver.DockerRegistryHost + `":{"` +
60+
`auth":"` + base64.StdEncoding.EncodeToString([]byte(testUsername+":"+testPassword)) + `"}}}`),
61+
},
62+
},
5263
}
5364

5465
for _, tt := range tests {
@@ -66,6 +77,9 @@ func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
6677
},
6778
Data: tt.secretData,
6879
}
80+
if tt.secretType != "" {
81+
secret.Type = tt.secretType
82+
}
6983

7084
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
7185

0 commit comments

Comments
 (0)