-
-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for pulling images from sources requiring authentication. The feature adheres to `imagePullSecrets` in `Pod`s and `ServiceAccount`s. Loosely based on some of @DolevAlgam's work in #92 fixes #19
- Loading branch information
Showing
12 changed files
with
451 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package secrets | ||
|
||
import v1 "k8s.io/api/core/v1" | ||
|
||
// DummyImagePullSecretsProvider does nothing | ||
type DummyImagePullSecretsProvider struct { | ||
} | ||
|
||
func NewDummyImagePullSecretsProvider() ImagePullSecretsProvider { | ||
return &DummyImagePullSecretsProvider{} | ||
} | ||
|
||
func (p *DummyImagePullSecretsProvider) GetImagePullSecrets(pod *v1.Pod) (*ImagePullSecretsResult, error) { | ||
return NewImagePullSecretsResult(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package secrets | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"os" | ||
|
||
jsonpatch "github.com/evanphx/json-patch" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes" | ||
) | ||
|
||
// KubernetesImagePullSecretsProvider retrieves the secrets holding docker auth information from Kubernetes and merges | ||
// them if necessary. Supports Pod secrets as well as ServiceAccount secrets. | ||
type KubernetesImagePullSecretsProvider struct { | ||
kubernetesClient kubernetes.Interface | ||
} | ||
|
||
// ImagePullSecretsResult contains the result of GetImagePullSecrets | ||
type ImagePullSecretsResult struct { | ||
Secrets map[string][]byte | ||
Aggregate []byte | ||
} | ||
|
||
// NewImagePullSecretsResult initialises ImagePullSecretsResult | ||
func NewImagePullSecretsResult() *ImagePullSecretsResult { | ||
return &ImagePullSecretsResult{ | ||
Secrets: map[string][]byte{}, | ||
Aggregate: []byte("{}"), | ||
} | ||
} | ||
|
||
// Add adds a secrets to internal list and rebuilds the aggregate | ||
func (r *ImagePullSecretsResult) Add(name string, data []byte) { | ||
r.Secrets[name] = data | ||
r.Aggregate, _ = jsonpatch.MergePatch(r.Aggregate, data) | ||
} | ||
|
||
// AuthFile provides the aggregate as a file to be used by a docker client | ||
func (r *ImagePullSecretsResult) AuthFile() (*os.File, error) { | ||
tmpfile, err := ioutil.TempFile("", "auth") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if _, err := tmpfile.Write(r.Aggregate); err != nil { | ||
return nil, err | ||
} | ||
if err := tmpfile.Close(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return tmpfile, nil | ||
} | ||
|
||
func NewKubernetesImagePullSecretsProvider(clientset kubernetes.Interface) ImagePullSecretsProvider { | ||
return &KubernetesImagePullSecretsProvider{ | ||
kubernetesClient: clientset, | ||
} | ||
} | ||
|
||
// GetImagePullSecrets returns all secrets with their respective content | ||
func (p *KubernetesImagePullSecretsProvider) GetImagePullSecrets(pod *v1.Pod) (*ImagePullSecretsResult, error) { | ||
var secrets = make(map[string][]byte) | ||
|
||
// retrieve secret names from pod ServiceAccount (spec.imagePullSecrets) | ||
serviceAccount, err := p.kubernetesClient.CoreV1(). | ||
ServiceAccounts(pod.Namespace). | ||
Get(context.TODO(), pod.Spec.ServiceAccountName, metav1.GetOptions{}) | ||
if err != nil { | ||
// TODO: Handle error gracefully, dont panic | ||
return nil, err | ||
} | ||
|
||
imagePullSecrets := append(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets...) | ||
|
||
result := NewImagePullSecretsResult() | ||
for _, imagePullSecret := range imagePullSecrets { | ||
// fetch a secret only once | ||
if _, exists := secrets[imagePullSecret.Name]; exists { | ||
continue | ||
} | ||
|
||
secret, _ := p.kubernetesClient.CoreV1().Secrets(pod.Namespace).Get(context.TODO(), imagePullSecret.Name, metav1.GetOptions{}) | ||
|
||
if secret.Type != v1.SecretTypeDockerConfigJson { | ||
continue | ||
} | ||
|
||
result.Add(imagePullSecret.Name, secret.Data[v1.DockerConfigJsonKey]) | ||
} | ||
|
||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package secrets | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
//type ExampleTestSuite struct { | ||
// suite.Suite | ||
//} | ||
// | ||
//func (suite *ExampleTestSuite) SetupTest() { | ||
//} | ||
//func (suite *ExampleTestSuite) TestExample() { | ||
// assert.Equal(suite.T(), 5, 1) | ||
//} | ||
// | ||
//func TestExampleTestSuite(t *testing.T) { | ||
// suite.Run(t, new(ExampleTestSuite)) | ||
//} | ||
|
||
// Test: | ||
//+------------------+-----+----------------+ | ||
//| | Pod | ServiceAccount | | ||
//+------------------+-----+----------------+ | ||
//| ImagePullSecrets | Y | Y | | ||
//+------------------+-----+----------------+ | ||
//| ImagePullSecrets | Y | N | | ||
//+------------------+-----+----------------+ | ||
//| ImagePullSecrets | N | Y | | ||
//+------------------+-----+----------------+ | ||
//| ImagePullSecrets | N | N | | ||
//+------------------+-----+----------------+ | ||
// | ||
// Multple image pull secrets on pod + service account | ||
// Pod secret should override service account secret | ||
|
||
func TestKubernetesCredentialProvider_GetImagePullSecrets(t *testing.T) { | ||
clientSet := fake.NewSimpleClientset() | ||
|
||
svcAccount := &v1.ServiceAccount{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "my-service-account", | ||
}, | ||
ImagePullSecrets: []v1.LocalObjectReference{ | ||
{Name: "my-sa-secret"}, | ||
}, | ||
} | ||
svcAccountSecretDockerConfigJson := []byte(`{"auths":{"my-sa-secret.registry.example.com":{"username":"my-sa-secret","password":"xxxxxxxxxxx","email":"[email protected]","auth":"c3R...zE2"}}}`) | ||
svcAccountSecret := &v1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "my-sa-secret", | ||
}, | ||
Type: v1.SecretTypeDockerConfigJson, | ||
Data: map[string][]byte{ | ||
v1.DockerConfigJsonKey: svcAccountSecretDockerConfigJson, | ||
}, | ||
} | ||
podSecretDockerConfigJson := []byte(`{"auths":{"my-pod-secret.registry.example.com":{"username":"my-sa-secret","password":"xxxxxxxxxxx","email":"[email protected]","auth":"c3R...zE2"}}}`) | ||
podSecret := &v1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "my-pod-secret", | ||
}, | ||
Type: v1.SecretTypeDockerConfigJson, | ||
Data: map[string][]byte{ | ||
v1.DockerConfigJsonKey: podSecretDockerConfigJson, | ||
}, | ||
} | ||
pod := &v1.Pod{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: "test-ns", | ||
Name: "my-pod", | ||
}, | ||
Spec: v1.PodSpec{ | ||
ServiceAccountName: "my-service-account", | ||
ImagePullSecrets: []v1.LocalObjectReference{ | ||
{Name: "my-pod-secret"}, | ||
}, | ||
}, | ||
} | ||
|
||
_, _ = clientSet.CoreV1().ServiceAccounts("test-ns").Create(context.TODO(), svcAccount, metav1.CreateOptions{}) | ||
_, _ = clientSet.CoreV1().Secrets("test-ns").Create(context.TODO(), svcAccountSecret, metav1.CreateOptions{}) | ||
_, _ = clientSet.CoreV1().Secrets("test-ns").Create(context.TODO(), podSecret, metav1.CreateOptions{}) | ||
|
||
provider := NewKubernetesImagePullSecretsProvider(clientSet) | ||
result, err := provider.GetImagePullSecrets(pod) | ||
|
||
assert.NoError(t, err) | ||
assert.NotNil(t, result) | ||
assert.Len(t, result.Secrets, 2) | ||
assert.Equal(t, svcAccountSecretDockerConfigJson, result.Secrets["my-sa-secret"]) | ||
assert.Equal(t, podSecretDockerConfigJson, result.Secrets["my-pod-secret"]) | ||
} | ||
|
||
// TestImagePullSecretsResult_Add tests if aggregation works | ||
func TestImagePullSecretsResult_Add(t *testing.T) { | ||
expected := &ImagePullSecretsResult{ | ||
Secrets: map[string][]byte{ | ||
"foo": []byte("{\"foo\":\"123\"}"), | ||
"bar": []byte("{\"bar\":\"456\"}"), | ||
}, | ||
Aggregate: []byte("{\"bar\":\"456\",\"foo\":\"123\"}"), | ||
} | ||
|
||
r := NewImagePullSecretsResult() | ||
r.Add("foo", []byte("{\"foo\":\"123\"}")) | ||
r.Add("bar", []byte("{\"bar\":\"456\"}")) | ||
|
||
assert.Equal(t, r, expected) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package secrets | ||
|
||
import v1 "k8s.io/api/core/v1" | ||
|
||
type ImagePullSecretsProvider interface { | ||
GetImagePullSecrets(pod *v1.Pod) (*ImagePullSecretsResult, error) | ||
} |
Oops, something went wrong.