Skip to content

Commit

Permalink
Create a k8schain directly from pull secrets (#1049)
Browse files Browse the repository at this point in the history
* Create a k8schain directly from pull secrets

The k8schain package creates a new keychain by resolving Kubernets
secrets from service accounts and other image pull secrets specified by
name. The k8s client gets the full secret content for these named
resources.

Sometimes it's desirable to pull the content of a secret from a cache,
or to watch a resource for changes in a controller. In this situation
it would be helpful to create a k8schain directly from resolved secrets,
without making calls to the k8s api server.

This change refactors the existing k8schain.New() method to separate the
resolution of secrets from the creation of the keychain. This is only a
refactoring of existing behavior to shortcut the k8s api lookups. There
is no change in existing behavior for the keychain.

Added:

    k8schain.NewFromPullSecrets(context.Context, []corev1.Secret) (authn.Keychain, error)

Signed-off-by: Scott Andrews <andrewssc@vmware.com>

* gofmt

Signed-off-by: Scott Andrews <andrewssc@vmware.com>
  • Loading branch information
scothis committed Jun 24, 2021
1 parent acad0ed commit ce35c99
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 14 deletions.
34 changes: 20 additions & 14 deletions pkg/authn/k8schain/k8schain.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
credentialprovider "github.com/vdemeester/k8s-pkg-credentialprovider"
credentialprovidersecrets "github.com/vdemeester/k8s-pkg-credentialprovider/secrets"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -66,7 +66,7 @@ func New(ctx context.Context, client kubernetes.Interface, opt Options) (authn.K
// Pod's service account.

// First, fetch all of the explicitly declared pull secrets
var pullSecrets []v1.Secret
var pullSecrets []corev1.Secret
if client != nil {
for _, name := range opt.ImagePullSecrets {
ps, err := client.CoreV1().Secrets(opt.Namespace).Get(ctx, name, metav1.GetOptions{})
Expand All @@ -90,18 +90,7 @@ func New(ctx context.Context, client kubernetes.Interface, opt Options) (authn.K
}
}

once.Do(func() {
keyring = credentialprovider.NewDockerKeyring()
})

// Third, extend the default keyring with the pull secrets.
kr, err := credentialprovidersecrets.MakeDockerKeyring(pullSecrets, keyring)
if err != nil {
return nil, err
}
return &keychain{
keyring: kr,
}, nil
return NewFromPullSecrets(ctx, pullSecrets)
}

// NewInCluster returns a new authn.Keychain suitable for resolving image references as
Expand Down Expand Up @@ -132,6 +121,23 @@ func NewNoClient(ctx context.Context) (authn.Keychain, error) {
return New(ctx, nil, Options{})
}

// NewFromPullSecrets returns a new authn.Keychain suitable for resolving image references as
// scoped by the pull secrets.
func NewFromPullSecrets(ctx context.Context, pullSecrets []corev1.Secret) (authn.Keychain, error) {
once.Do(func() {
keyring = credentialprovider.NewDockerKeyring()
})

// Extend the default keyring with the pull secrets.
kr, err := credentialprovidersecrets.MakeDockerKeyring(pullSecrets, keyring)
if err != nil {
return nil, err
}
return &keychain{
keyring: kr,
}, nil
}

type lazyProvider struct {
kc *keychain
image string
Expand Down
65 changes: 65 additions & 0 deletions pkg/authn/k8schain/k8schain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,68 @@ func TestImagePullSecrets(t *testing.T) {
})
}
}

func TestFromPullSecrets(t *testing.T) {
username, password := "foo", "bar"
specificUser, specificPass := "very", "specific"

pullSecrets := []corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "ns",
},
Type: corev1.SecretTypeDockercfg,
Data: map[string][]byte{
corev1.DockerConfigKey: []byte(
fmt.Sprintf(`{"fake.registry.io": {"auth": %q}, "fake.registry.io/more/specific": {"auth": %q}}`,
base64.StdEncoding.EncodeToString([]byte(username+":"+password)),
base64.StdEncoding.EncodeToString([]byte(specificUser+":"+specificPass))),
),
},
},
}

kc, err := NewFromPullSecrets(context.Background(), pullSecrets)
if err != nil {
t.Fatalf("NewFromPullSecrets() = %v", err)
}

repo, err := name.NewRepository("fake.registry.io/more/specific", name.WeakValidation)
if err != nil {
t.Errorf("NewRegistry() = %v", err)
}

for _, tc := range []struct {
name string
auth authn.Authenticator
target authn.Resource
}{{
name: "registry",
auth: &authn.Basic{Username: username, Password: password},
target: repo.Registry,
}, {
name: "repo",
auth: &authn.Basic{Username: specificUser, Password: specificPass},
target: repo,
}} {
t.Run(tc.name, func(t *testing.T) {
tc := tc
auth, err := kc.Resolve(tc.target)
if err != nil {
t.Errorf("Resolve(%v) = %v", tc.target, err)
}
got, err := auth.Authorization()
if err != nil {
t.Errorf("Authorization() = %v", err)
}
want, err := tc.auth.Authorization()
if err != nil {
t.Errorf("Authorization() = %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Resolve() = %v, want %v", got, want)
}
})
}
}

0 comments on commit ce35c99

Please sign in to comment.