Merge pull request #74 from rusenask/feature/helm_secrets_improvement
Feature/helm secrets improvementpull/75/head
commit
4617a5a341
8
Makefile
8
Makefile
|
@ -14,3 +14,11 @@ test:
|
|||
build:
|
||||
@echo "++ Building keel"
|
||||
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
|
||||
|
|
@ -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"
|
||||
|
@ -72,9 +75,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 +130,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
|
||||
}
|
||||
|
||||
|
@ -150,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,
|
||||
|
@ -163,12 +210,36 @@ 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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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{
|
||||
|
@ -122,3 +173,95 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue