From aaf795418ed31fcbe337be17300087be81eb071d Mon Sep 17 00:00:00 2001 From: Karolis Rusenas Date: Thu, 3 Aug 2017 23:02:01 +0100 Subject: [PATCH 1/2] more logs for debugging private registries --- Makefile | 10 +++++++++- secrets/secrets.go | 30 ++++++++++++++++++++++++++++- secrets/secrets_test.go | 42 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5fa1449ff..41ee3789c 100644 --- a/Makefile +++ b/Makefile @@ -13,4 +13,12 @@ test: build: @echo "++ Building keel" - CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags "$(LDFLAGS)" -o keel . \ No newline at end of file + CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags "$(LDFLAGS)" -o keel . + +image: + docker build -t karolisr/keel:alpha -f Dockerfile . + +alpha: image + @echo "++ Pushing keel alpha" + docker push karolisr/keel:alpha + \ No newline at end of file diff --git a/secrets/secrets.go b/secrets/secrets.go index d2a76b550..829c3bb22 100644 --- a/secrets/secrets.go +++ b/secrets/secrets.go @@ -72,9 +72,28 @@ func (g *DefaultGetter) lookupSecrets(image *types.TrackedImage) ([]string, erro for _, pod := range podList.Items { podSecrets := getPodImagePullSecrets(&pod) + log.WithFields(log.Fields{ + "namespace": image.Namespace, + "provider": image.Provider, + "registry": image.Image.Registry(), + "image": image.Image.Repository(), + "pod_selector": selector, + "secrets": podSecrets, + }).Info("secrets.defaultGetter.lookupSecrets: pod secrets found") secrets = append(secrets, podSecrets...) } + if len(secrets) == 0 { + log.WithFields(log.Fields{ + "namespace": image.Namespace, + "provider": image.Provider, + "registry": image.Image.Registry(), + "image": image.Image.Repository(), + "pod_selector": selector, + "pods_checked": len(podList.Items), + }).Info("secrets.defaultGetter.lookupSecrets: no secrets for image found") + } + return secrets, nil } @@ -108,7 +127,7 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty "namespace": image.Namespace, "secret_ref": secretRef, "type": secret.Type, - }).Warn("secrets.defaultGetter: supplied secret is not kubernetes.io/dockerconfigjson, ignoring") + }).Warn("secrets.defaultGetter: supplied secret is not kubernetes.io/dockercfg, ignoring") continue } @@ -163,7 +182,16 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty return credentials, nil } } + } + if len(image.Secrets) > 0 { + log.WithFields(log.Fields{ + "namespace": image.Namespace, + "provider": image.Provider, + "registry": image.Image.Registry(), + "image": image.Image.Repository(), + "secrets": image.Secrets, + }).Warn("secrets.defaultGetter.lookupSecrets: docker credentials were not found among secrets") } return credentials, nil diff --git a/secrets/secrets_test.go b/secrets/secrets_test.go index beca56036..dbb5c138e 100644 --- a/secrets/secrets_test.go +++ b/secrets/secrets_test.go @@ -122,3 +122,45 @@ func TestLookupHelmSecret(t *testing.T) { t.Errorf("unexpected pass: %s", creds.Password) } } + +func TestLookupHelmNoSecretsFound(t *testing.T) { + imgRef, _ := image.Parse("karolisr/webhook-demo:0.0.11") + + impl := &testutil.FakeK8sImplementer{ + AvailablePods: &v1.PodList{ + Items: []v1.Pod{ + v1.Pod{ + Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{ + v1.LocalObjectReference{ + Name: "very-secret", + }, + }, + }, + }, + }, + }, + Error: fmt.Errorf("not found"), + } + + getter := NewGetter(impl) + + trackedImage := &types.TrackedImage{ + Image: imgRef, + Namespace: "default", + Secrets: []string{"myregistrysecret"}, + } + + creds, err := getter.Get(trackedImage) + if err != nil { + t.Errorf("failed to get creds: %s", err) + } + + // should be anonymous + if creds.Username != "" { + t.Errorf("unexpected username: %s", creds.Username) + } + + if creds.Password != "" { + t.Errorf("unexpected pass: %s", creds.Password) + } +} From ffefe646bd152004f2e45906fa279e001ca717d9 Mon Sep 17 00:00:00 2001 From: Karolis Rusenas Date: Thu, 3 Aug 2017 23:27:47 +0100 Subject: [PATCH 2/2] support for base64 encoded secrets --- secrets/secrets.go | 47 ++++++++++++++++- secrets/secrets_test.go | 109 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/secrets/secrets.go b/secrets/secrets.go index 829c3bb22..1a7f76880 100644 --- a/secrets/secrets.go +++ b/secrets/secrets.go @@ -1,9 +1,12 @@ package secrets import ( + "encoding/base64" "encoding/json" "errors" + "fmt" "net/url" + "strings" "github.com/rusenask/keel/provider/helm" "github.com/rusenask/keel/provider/kubernetes" @@ -169,8 +172,33 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty } if h == image.Image.Registry() { - credentials.Username = auth.Username - credentials.Password = auth.Password + if auth.Username != "" && auth.Password != "" { + credentials.Username = auth.Username + credentials.Password = auth.Password + } else if auth.Auth != "" { + username, password, err := decodeBase64Secret(auth.Auth) + if err != nil { + log.WithFields(log.Fields{ + "image": image.Image.Repository(), + "namespace": image.Namespace, + "registry": registry, + "secret_ref": secretRef, + "error": err, + }).Error("secrets.defaultGetter: failed to decode auth secret") + continue + } + credentials.Username = username + credentials.Password = password + } else { + log.WithFields(log.Fields{ + "image": image.Image.Repository(), + "namespace": image.Namespace, + "registry": registry, + "secret_ref": secretRef, + "error": err, + }).Warn("secrets.defaultGetter: secret doesn't have username, password and base64 encoded auth, skipping") + continue + } log.WithFields(log.Fields{ "namespace": image.Namespace, @@ -197,6 +225,21 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty return credentials, nil } +func decodeBase64Secret(authSecret string) (username, password string, err error) { + decoded, err := base64.StdEncoding.DecodeString(authSecret) + if err != nil { + return + } + + parts := strings.Split(string(decoded), ":") + + if len(parts) != 2 { + return "", "", fmt.Errorf("unexpected auth secret format") + } + + return parts[0], parts[1], nil +} + func hostname(registry string) (string, error) { u, err := url.Parse(registry) if err != nil { diff --git a/secrets/secrets_test.go b/secrets/secrets_test.go index dbb5c138e..347ff40ed 100644 --- a/secrets/secrets_test.go +++ b/secrets/secrets_test.go @@ -1,19 +1,22 @@ package secrets import ( + "encoding/base64" "fmt" + "testing" "github.com/rusenask/keel/types" "github.com/rusenask/keel/util/image" - - "k8s.io/client-go/pkg/api/v1" - testutil "github.com/rusenask/keel/util/testing" - "testing" + "k8s.io/client-go/pkg/api/v1" ) var secretDataPayload = `{"https://index.docker.io/v1/":{"username":"user-x","password":"pass-x","email":"karolis.rusenas@gmail.com","auth":"somethinghere"}}` +func mustEncode(data string) string { + return base64.StdEncoding.EncodeToString([]byte(data)) +} + func TestGetSecret(t *testing.T) { imgRef, _ := image.Parse("karolisr/webhook-demo:0.0.11") @@ -77,9 +80,57 @@ func TestGetSecretNotFound(t *testing.T) { } } +var secretDataPayloadEncoded = `{"https://index.docker.io/v1/":{"auth": "%s"}}` + func TestLookupHelmSecret(t *testing.T) { imgRef, _ := image.Parse("karolisr/webhook-demo:0.0.11") + impl := &testutil.FakeK8sImplementer{ + AvailablePods: &v1.PodList{ + Items: []v1.Pod{ + v1.Pod{ + Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{ + v1.LocalObjectReference{ + Name: "very-secret", + }, + }, + }, + }, + }, + }, + AvailableSecret: &v1.Secret{ + Data: map[string][]byte{ + dockerConfigJSONKey: []byte(fmt.Sprintf(secretDataPayloadEncoded, mustEncode("user-y:pass-y"))), + }, + Type: v1.SecretTypeDockercfg, + }, + } + + getter := NewGetter(impl) + + trackedImage := &types.TrackedImage{ + Image: imgRef, + Namespace: "default", + Secrets: []string{"myregistrysecret"}, + } + + creds, err := getter.Get(trackedImage) + if err != nil { + t.Errorf("failed to get creds: %s", err) + } + + if creds.Username != "user-y" { + t.Errorf("unexpected username: %s", creds.Username) + } + + if creds.Password != "pass-y" { + t.Errorf("unexpected pass: %s", creds.Password) + } +} + +func TestLookupHelmEncodedSecret(t *testing.T) { + imgRef, _ := image.Parse("karolisr/webhook-demo:0.0.11") + impl := &testutil.FakeK8sImplementer{ AvailablePods: &v1.PodList{ Items: []v1.Pod{ @@ -164,3 +215,53 @@ func TestLookupHelmNoSecretsFound(t *testing.T) { t.Errorf("unexpected pass: %s", creds.Password) } } + +func Test_decodeBase64Secret(t *testing.T) { + type args struct { + authSecret string + } + tests := []struct { + name string + args args + wantUsername string + wantPassword string + wantErr bool + }{ + { + name: "hello there", + args: args{authSecret: "aGVsbG86dGhlcmU="}, + wantUsername: "hello", + wantPassword: "there", + wantErr: false, + }, + { + name: "hello there, encoded", + args: args{authSecret: mustEncode("hello:there")}, + wantUsername: "hello", + wantPassword: "there", + wantErr: false, + }, + { + name: "empty", + args: args{authSecret: ""}, + wantUsername: "", + wantPassword: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotUsername, gotPassword, err := decodeBase64Secret(tt.args.authSecret) + if (err != nil) != tt.wantErr { + t.Errorf("decodeBase64Secret() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotUsername != tt.wantUsername { + t.Errorf("decodeBase64Secret() gotUsername = %v, want %v", gotUsername, tt.wantUsername) + } + if gotPassword != tt.wantPassword { + t.Errorf("decodeBase64Secret() gotPassword = %v, want %v", gotPassword, tt.wantPassword) + } + }) + } +}