From e882f44ae99943c70c162cd6c98bdf1ecb8386fd Mon Sep 17 00:00:00 2001 From: Gerard Nguyen Date: Wed, 3 Jul 2024 08:06:37 +1000 Subject: [PATCH] Gerard/sc 106216/b registry image collector (#1570) * update registry auth with username and password * add unit test --- pkg/collect/registry.go | 24 +++++++++++-- pkg/collect/registry_test.go | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 pkg/collect/registry_test.go diff --git a/pkg/collect/registry.go b/pkg/collect/registry.go index 1f4d7254..178f7e7e 100644 --- a/pkg/collect/registry.go +++ b/pkg/collect/registry.go @@ -21,6 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/klog/v2" ) type RegistryImage struct { @@ -97,6 +98,7 @@ func imageExists(namespace string, clientConfig *rest.Config, registryCollector authConfig, err := getImageAuthConfig(namespace, clientConfig, registryCollector, imageRef) if err != nil { + klog.Errorf("failed to get auth config: %v", err) return false, errors.Wrap(err, "failed to get auth config") } @@ -115,10 +117,13 @@ func imageExists(namespace string, clientConfig *rest.Config, registryCollector remoteImage, err := imageRef.NewImage(context.Background(), &sysCtx) if err == nil { + klog.Infof("image %s exists", image) remoteImage.Close() return true, nil } + klog.Errorf("failed to get image %s: %v", image, err) + if strings.Contains(err.Error(), "no image found in manifest list for architecture") { // manifest was downloaded, but no matching architecture found in manifest // should this count as image does not exist? @@ -188,7 +193,9 @@ func getImageAuthConfigFromData(imageRef types.ImageReference, pullSecrets *v1be dockerCfgJSON := struct { Auths map[string]struct { - Auth []byte `json:"auth"` + Auth string `json:"auth"` + Username string `json:"username"` + Password string `json:"password"` } `json:"auths"` }{} @@ -203,14 +210,25 @@ func getImageAuthConfigFromData(imageRef types.ImageReference, pullSecrets *v1be return nil, nil } + // gcr.io auth uses username and password, e.g. username: _json_key, password: + if auth.Username != "" && auth.Password != "" { + return ®istryAuthConfig{ + username: auth.Username, + password: auth.Password, + }, nil + } + + // docker.io auth uses auth, e.g. auth: + // username and password can't contain colon + // at least according to https://github.com/docker/cli/blob/v27.0.3/cli/config/configfile/file.go#L247 parts := strings.Split(string(auth.Auth), ":") if len(parts) != 2 { - return nil, errors.Errorf("expected 2 parts in the string, but found %d", len(parts)) + return nil, errors.Errorf("expected 2 parts in the auth string, but found %d", len(parts)) } authConfig := registryAuthConfig{ username: parts[0], - password: parts[1], + password: strings.Trim(parts[1], "\x00"), } return &authConfig, nil diff --git a/pkg/collect/registry_test.go b/pkg/collect/registry_test.go new file mode 100644 index 00000000..f20f51c7 --- /dev/null +++ b/pkg/collect/registry_test.go @@ -0,0 +1,70 @@ +package collect + +import ( + "encoding/base64" + "fmt" + "testing" + + "github.com/containers/image/v5/transports/alltransports" + "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/stretchr/testify/assert" +) + +func TestGetImageAuthConfigFromData(t *testing.T) { + tests := []struct { + name string + imageName string + dockerConfigJSON string + expectedUsername string + expectedPassword string + expectedError bool + }{ + { + name: "docker.io auth", + imageName: "docker.io/myimage", + dockerConfigJSON: `{"auths":{"docker.io":{"auth":"username:password"}}}`, + expectedUsername: "username", + expectedPassword: "password", + expectedError: false, + }, + { + name: "docker.io auth multi colon", + imageName: "docker.io/myimage", + dockerConfigJSON: `{"auths":{"docker.io":{"auth":"user:name:pass:word"}}}`, + expectedError: true, + }, + { + name: "gcr.io auth", + imageName: "gcr.io/myimage", + dockerConfigJSON: `{"auths":{"gcr.io":{"username":"_json_key","password":"sa-key"}}}`, + expectedUsername: "_json_key", + expectedPassword: "sa-key", + expectedError: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", test.imageName)) + assert.NoError(t, err) + + pullSecrets := &v1beta2.ImagePullSecrets{ + SecretType: "kubernetes.io/dockerconfigjson", + Data: map[string]string{ + ".dockerconfigjson": base64.StdEncoding.EncodeToString([]byte(test.dockerConfigJSON)), + }, + } + + authConfig, err := getImageAuthConfigFromData(imageRef, pullSecrets) + if test.expectedError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.NotNil(t, authConfig) + assert.Equal(t, test.expectedUsername, authConfig.username) + assert.Equal(t, test.expectedPassword, authConfig.password) + }) + } +}