Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Get image pull secrets via serviceAccounts #1291

Merged
merged 6 commits into from
Aug 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions cluster/kubernetes/images.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package kubernetes

import (
"fmt"

"github.com/go-kit/kit/log"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/weaveworks/flux"
"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/registry"
)

func mergeCredentials(log func(...interface{}) error, client extendedClient, namespace string, podTemplate apiv1.PodTemplateSpec, imageCreds registry.ImageCreds, seenCreds map[string]registry.Credentials) {
creds := registry.NoCredentials()
var imagePullSecrets []string
saName := podTemplate.Spec.ServiceAccountName
if saName == "" {
saName = "default"
}

sa, err := client.CoreV1().ServiceAccounts(namespace).Get(saName, meta_v1.GetOptions{})
if err == nil {
for _, ips := range sa.ImagePullSecrets {
imagePullSecrets = append(imagePullSecrets, ips.Name)
}
}

for _, imagePullSecret := range podTemplate.Spec.ImagePullSecrets {
imagePullSecrets = append(imagePullSecrets, imagePullSecret.Name)
}

for _, name := range imagePullSecrets {
if seen, ok := seenCreds[name]; ok {
creds.Merge(seen)
continue
}

secret, err := client.CoreV1().Secrets(namespace).Get(name, meta_v1.GetOptions{})
if err != nil {
log("err", errors.Wrapf(err, "getting secret %q from namespace %q", name, namespace))
seenCreds[name] = registry.NoCredentials()
continue
}

var decoded []byte
var ok bool
// These differ in format; but, ParseCredentials will
// handle either.
switch apiv1.SecretType(secret.Type) {
case apiv1.SecretTypeDockercfg:
decoded, ok = secret.Data[apiv1.DockerConfigKey]
case apiv1.SecretTypeDockerConfigJson:
decoded, ok = secret.Data[apiv1.DockerConfigJsonKey]
default:
log("skip", "unknown type", "secret", namespace+"/"+secret.Name, "type", secret.Type)
seenCreds[name] = registry.NoCredentials()
continue
}

if !ok {
log("err", errors.Wrapf(err, "retrieving pod secret %q", secret.Name))
seenCreds[name] = registry.NoCredentials()
continue
}

// Parse secret
crd, err := registry.ParseCredentials(fmt.Sprintf("%s:secret/%s", namespace, name), decoded)
if err != nil {
log("err", err.Error())
seenCreds[name] = registry.NoCredentials()
continue
}
seenCreds[name] = crd

// Merge into the credentials for this PodSpec
creds.Merge(crd)
}

// Now create the service and attach the credentials
for _, container := range podTemplate.Spec.Containers {
r, err := image.ParseRef(container.Image)
if err != nil {
log("err", err.Error())
continue
}
imageCreds[r.Name] = creds
}
}

// ImagesToFetch is a k8s specific method to get a list of images to update along with their credentials
func (c *Cluster) ImagesToFetch() registry.ImageCreds {
allImageCreds := make(registry.ImageCreds)

namespaces, err := c.getAllowedNamespaces()
if err != nil {
c.logger.Log("err", errors.Wrap(err, "getting namespaces"))
return allImageCreds
}

for _, ns := range namespaces {
seenCreds := make(map[string]registry.Credentials)
for kind, resourceKind := range resourceKinds {
podControllers, err := resourceKind.getPodControllers(c, ns.Name)
if err != nil {
if se, ok := err.(*apierrors.StatusError); ok && se.ErrStatus.Reason == meta_v1.StatusReasonNotFound {
// Kind not supported by API server, skip
} else {
c.logger.Log("err", errors.Wrapf(err, "getting kind %s for namespace %s", kind, ns.Name))
}
continue
}

imageCreds := make(registry.ImageCreds)
for _, podController := range podControllers {
logger := log.With(c.logger, "resource", flux.MakeResourceID(ns.Name, kind, podController.name))
mergeCredentials(logger.Log, c.client, ns.Name, podController.podTemplate, imageCreds, seenCreds)
}

// Merge creds
for imageID, creds := range imageCreds {
existingCreds, ok := allImageCreds[imageID]
if ok {
existingCreds.Merge(creds)
} else {
allImageCreds[imageID] = creds
}
}
}
}

return allImageCreds
}
76 changes: 76 additions & 0 deletions cluster/kubernetes/images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package kubernetes

import (
"encoding/base64"
"testing"

"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/fake"

"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/registry"
)

func noopLog(...interface{}) error {
return nil
}

func makeImagePullSecret(ns, name, host string) *apiv1.Secret {
imagePullSecret := apiv1.Secret{Type: apiv1.SecretTypeDockerConfigJson}
imagePullSecret.Name = name
imagePullSecret.Namespace = ns
imagePullSecret.Data = map[string][]byte{
apiv1.DockerConfigJsonKey: []byte(`
{
"auths": {
"` + host + `": {
"auth": "` + base64.StdEncoding.EncodeToString([]byte("user:passwd")) + `"
}
}
}`),
}
return &imagePullSecret
}

func makeServiceAccount(ns, name string, imagePullSecretNames []string) *apiv1.ServiceAccount {
sa := apiv1.ServiceAccount{}
sa.Namespace = ns
sa.Name = name
for _, ips := range imagePullSecretNames {
sa.ImagePullSecrets = append(sa.ImagePullSecrets, apiv1.LocalObjectReference{Name: ips})
}
return &sa
}

func TestMergeCredentials(t *testing.T) {
ns, secretName1, secretName2 := "foo-ns", "secret-creds", "secret-sa-creds"
saName := "service-account"
ref, _ := image.ParseRef("foo/bar:tag")
spec := apiv1.PodTemplateSpec{
Spec: apiv1.PodSpec{
ServiceAccountName: saName,
ImagePullSecrets: []apiv1.LocalObjectReference{
{Name: secretName1},
},
Containers: []apiv1.Container{
{Name: "container1", Image: ref.String()},
},
},
}

clientset := fake.NewSimpleClientset(
makeServiceAccount(ns, saName, []string{secretName2}),
makeImagePullSecret(ns, secretName1, "docker.io"),
makeImagePullSecret(ns, secretName2, "quay.io"))
client := extendedClient{clientset, nil}

creds := registry.ImageCreds{}
mergeCredentials(noopLog, client, ns, spec, creds, make(map[string]registry.Credentials))

// check that we accumulated some credentials
assert.Contains(t, creds, ref.Name)
c := creds[ref.Name]
hosts := c.Hosts()
assert.ElementsMatch(t, []string{"docker.io", "quay.io"}, hosts)
}
Loading