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

Commit

Permalink
Merge pull request #1291 from weaveworks/issues/1043-get-image-secret…
Browse files Browse the repository at this point in the history
…s-from-sa

Get image pull secrets via serviceAccounts
  • Loading branch information
squaremo authored Aug 21, 2018
2 parents a8ff958 + fbcfddf commit 3e98ee1
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 136 deletions.
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

0 comments on commit 3e98ee1

Please sign in to comment.