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 <mail@makk.es>
This commit is contained in:
parent
1070d1287a
commit
bb4d886ba2
|
@ -492,7 +492,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
|
|||
}
|
||||
|
||||
// Build registryClient options from secret
|
||||
logOpt, err := loginOptionFromSecret(*secret)
|
||||
logOpt, err := loginOptionFromSecret(repo.Spec.URL, *secret)
|
||||
if err != nil {
|
||||
e := &serror.Event{
|
||||
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),
|
||||
|
|
|
@ -19,6 +19,7 @@ package controllers
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -825,6 +826,35 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
|
|||
assertFunc func(g *WithT, obj *sourcev1.HelmChart, build chart.Build)
|
||||
cleanFunc func(g *WithT, build *chart.Build)
|
||||
}{
|
||||
{
|
||||
name: "Reconciles chart build with docker repository credentials",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "auth",
|
||||
},
|
||||
Type: corev1.SecretTypeDockerConfigJson,
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": []byte(`{"auths":{"` +
|
||||
testRegistryserver.DockerRegistryHost + `":{"` +
|
||||
`auth":"` + base64.StdEncoding.EncodeToString([]byte(testUsername+":"+testPassword)) + `"}}}`),
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) {
|
||||
obj.Spec.Chart = metadata.Name
|
||||
obj.Spec.Version = metadata.Version
|
||||
repository.Spec.SecretRef = &meta.LocalObjectReference{Name: "auth"}
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertFunc: func(g *WithT, _ *sourcev1.HelmChart, build chart.Build) {
|
||||
g.Expect(build.Name).To(Equal(metadata.Name))
|
||||
g.Expect(build.Version).To(Equal(metadata.Version))
|
||||
g.Expect(build.Path).ToNot(BeEmpty())
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
},
|
||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Reconciles chart build with repository credentials",
|
||||
secret: &corev1.Secret{
|
||||
|
|
|
@ -17,12 +17,15 @@ limitations under the License.
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/conditions"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
|
@ -273,7 +276,7 @@ func (r *HelmRepositoryOCIReconciler) reconcileSource(ctx context.Context, obj *
|
|||
}
|
||||
|
||||
// Construct actual options
|
||||
logOpt, err := loginOptionFromSecret(secret)
|
||||
logOpt, err := loginOptionFromSecret(obj.Spec.URL, secret)
|
||||
if err != nil {
|
||||
e := &serror.Event{
|
||||
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
|
|||
return sreconcile.ResultSuccess, nil
|
||||
}
|
||||
|
||||
func loginOptionFromSecret(secret corev1.Secret) (registry.LoginOption, error) {
|
||||
username, password := string(secret.Data["username"]), string(secret.Data["password"])
|
||||
// loginOptionFromSecret derives authentication data from a Secret to login to an OCI registry. This Secret
|
||||
// may either hold "username" and "password" fields or be of the corev1.SecretTypeDockerConfigJson type and hold
|
||||
// a corev1.DockerConfigJsonKey field with a complete Docker configuration. If both, "username" and "password" are
|
||||
// empty, a nil LoginOption and a nil error will be returned.
|
||||
func loginOptionFromSecret(registryURL string, secret corev1.Secret) (registry.LoginOption, error) {
|
||||
var username, password string
|
||||
if secret.Type == corev1.SecretTypeDockerConfigJson {
|
||||
dockerCfg, err := config.LoadFromReader(bytes.NewReader(secret.Data[corev1.DockerConfigJsonKey]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load Docker config: %w", err)
|
||||
}
|
||||
parsedURL, err := url.Parse(registryURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse registry URL: %w", err)
|
||||
}
|
||||
authConfig, err := dockerCfg.GetAuthConfig(parsedURL.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get authentication data from Secret: %w", err)
|
||||
}
|
||||
username = authConfig.Username
|
||||
password = authConfig.Password
|
||||
} else {
|
||||
username, password = string(secret.Data["username"]), string(secret.Data["password"])
|
||||
}
|
||||
switch {
|
||||
case username == "" && password == "":
|
||||
return nil, nil
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
|
@ -36,6 +37,7 @@ import (
|
|||
func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
secretType corev1.SecretType
|
||||
secretData map[string][]byte
|
||||
}{
|
||||
{
|
||||
|
@ -49,6 +51,15 @@ func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
|
|||
name: "no auth data",
|
||||
secretData: nil,
|
||||
},
|
||||
{
|
||||
name: "dockerconfigjson Secret",
|
||||
secretType: corev1.SecretTypeDockerConfigJson,
|
||||
secretData: map[string][]byte{
|
||||
".dockerconfigjson": []byte(`{"auths":{"` +
|
||||
testRegistryserver.DockerRegistryHost + `":{"` +
|
||||
`auth":"` + base64.StdEncoding.EncodeToString([]byte(testUsername+":"+testPassword)) + `"}}}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -66,6 +77,9 @@ func TestHelmRepositoryOCIReconciler_Reconcile(t *testing.T) {
|
|||
},
|
||||
Data: tt.secretData,
|
||||
}
|
||||
if tt.secretType != "" {
|
||||
secret.Type = tt.secretType
|
||||
}
|
||||
|
||||
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
|
||||
|
||||
|
|
Loading…
Reference in New Issue