Skip to content

Update Keychain interface to accept repo #510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 10, 2019
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
4 changes: 1 addition & 3 deletions pkg/authn/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"fmt"
"os/exec"
"strings"

"github.com/google/go-containerregistry/pkg/name"
)

// magicNotFoundMessage is the string that the CLI special cases to mean
Expand All @@ -46,7 +44,7 @@ func (dr *defaultRunner) Run(cmd *exec.Cmd) error {
// helper executes the named credential helper against the given domain.
type helper struct {
name string
domain name.Registry
domain string

// We add this layer of indirection to facilitate unit testing.
r runner
Expand Down
4 changes: 1 addition & 3 deletions pkg/authn/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import (
"errors"
"os/exec"
"testing"

"github.com/google/go-containerregistry/pkg/name"
)

var (
testDomain, _ = name.NewRegistry("foo.dev", name.WeakValidation)
testDomain = "foo.dev"
)

// errorRunner implements runner to always return an execution error.
Expand Down
15 changes: 11 additions & 4 deletions pkg/authn/k8schain/k8schain.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,17 @@ type keychain struct {
}

// Resolve implements authn.Keychain
func (kc *keychain) Resolve(reg name.Registry) (authn.Authenticator, error) {
// TODO(mattmoor): Lookup expects an image reference and we only have a registry,
// find something better than this.
creds, found := kc.keyring.Lookup(reg.String() + "/foo/bar")
func (kc *keychain) Resolve(target authn.Resource) (authn.Authenticator, error) {
var (
creds []credentialprovider.LazyAuthConfiguration
found bool
)
if repo, ok := target.(name.Repository); ok {
creds, found = kc.keyring.Lookup(repo.String())
} else {
// Lookup expects an image reference and we only have a registry.
creds, found = kc.keyring.Lookup(target.RegistryStr() + "/foo/bar")
}
if !found || len(creds) < 1 {
return authn.Anonymous, nil
}
Expand Down
53 changes: 36 additions & 17 deletions pkg/authn/k8schain/k8schain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func TestAttachedServiceAccount(t *testing.T) {

func TestImagePullSecrets(t *testing.T) {
username, password := "foo", "bar"
specificUser, specificPass := "very", "specific"
client := fakeclient.NewSimpleClientset(&corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Expand All @@ -122,8 +123,9 @@ func TestImagePullSecrets(t *testing.T) {
Type: corev1.SecretTypeDockercfg,
Data: map[string][]byte{
corev1.DockerConfigKey: []byte(
fmt.Sprintf(`{"fake.registry.io": {"auth": "%s"}}`,
base64.StdEncoding.EncodeToString([]byte(username+":"+password))),
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))),
),
},
})
Expand All @@ -136,24 +138,41 @@ func TestImagePullSecrets(t *testing.T) {
t.Fatalf("New() = %v", err)
}

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

auth, err := kc.Resolve(reg)
if err != nil {
t.Errorf("Resolve(%v) = %v", reg, err)
}
got, err := auth.Authorization()
if err != nil {
t.Errorf("Authorization() = %v", err)
}
want, err := (&authn.Basic{Username: username, Password: password}).Authorization()
if err != nil {
t.Errorf("Authorization() = %v", err)
}
if got != want {
t.Errorf("Resolve() = %v, want %v", got, want)
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 got != want {
t.Errorf("Resolve() = %v, want %v", got, want)
}
})
}
}
27 changes: 19 additions & 8 deletions pkg/authn/keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,24 @@ import (
"runtime"

"github.com/google/go-containerregistry/pkg/logs"
"github.com/google/go-containerregistry/pkg/name"
)

// Resource represents a registry or repository that can be authenticated against.
type Resource interface {
// String returns the full string representation of the target, e.g.
// gcr.io/my-project or just gcr.io.
String() string

// RegistryStr returns just the registry portion of the target, e.g. for
// gcr.io/my-project, this should just return gcr.io. This is needed to
// pull out an appropriate hostname.
RegistryStr() string
}

// Keychain is an interface for resolving an image reference to a credential.
type Keychain interface {
// Resolve looks up the most appropriate credential for the specified registry.
Resolve(name.Registry) (Authenticator, error)
// Resolve looks up the most appropriate credential for the specified target.
Resolve(Resource) (Authenticator, error)
}

// defaultKeychain implements Keychain with the semantics of the standard Docker
Expand Down Expand Up @@ -97,7 +108,7 @@ var (
)

// Resolve implements Keychain.
func (dk *defaultKeychain) Resolve(reg name.Registry) (Authenticator, error) {
func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
dir, err := configDir()
if err != nil {
logs.Warn.Printf("Unable to determine config dir: %v", err)
Expand All @@ -119,21 +130,21 @@ func (dk *defaultKeychain) Resolve(reg name.Registry) (Authenticator, error) {
// Per-registry credential helpers take precedence.
if cf.CredHelper != nil {
for _, form := range domainForms {
if entry, ok := cf.CredHelper[fmt.Sprintf(form, reg.Name())]; ok {
return &helper{name: entry, domain: reg, r: &defaultRunner{}}, nil
if entry, ok := cf.CredHelper[fmt.Sprintf(form, target.RegistryStr())]; ok {
return &helper{name: entry, domain: target.RegistryStr(), r: &defaultRunner{}}, nil
}
}
}

// A global credential helper is next in precedence.
if cf.CredStore != "" {
return &helper{name: cf.CredStore, domain: reg, r: &defaultRunner{}}, nil
return &helper{name: cf.CredStore, domain: target.RegistryStr(), r: &defaultRunner{}}, nil
}

// Lastly, the 'auths' section directly contains basic auth entries.
if cf.Auths != nil {
for _, form := range domainForms {
if entry, ok := cf.Auths[fmt.Sprintf(form, reg.Name())]; ok {
if entry, ok := cf.Auths[fmt.Sprintf(form, target.RegistryStr())]; ok {
if entry.Auth != "" {
return &auth{entry.Auth}, nil
} else if entry.Username != "" {
Expand Down
4 changes: 2 additions & 2 deletions pkg/authn/keychain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ func checkHelper(t *testing.T) {
if help.name != "test" {
t.Errorf("Resolve().name; got %v, want \"test\"", help.name)
}
if help.domain != testRegistry {
t.Errorf("Resolve().domain; got %v, want %v", help.domain, testRegistry)
if help.domain != testRegistry.RegistryStr() {
t.Errorf("Resolve().domain; got %v, want %v", help.domain, testRegistry.RegistryStr())
}
}

Expand Down
8 changes: 2 additions & 6 deletions pkg/authn/multikeychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@

package authn

import (
"github.com/google/go-containerregistry/pkg/name"
)

type multiKeychain struct {
keychains []Keychain
}
Expand All @@ -31,9 +27,9 @@ func NewMultiKeychain(kcs ...Keychain) Keychain {
}

// Resolve implements Keychain.
func (mk *multiKeychain) Resolve(reg name.Registry) (Authenticator, error) {
func (mk *multiKeychain) Resolve(target Resource) (Authenticator, error) {
for _, kc := range mk.keychains {
auth, err := kc.Resolve(reg)
auth, err := kc.Resolve(target)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/authn/multikeychain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ func TestMultiKeychain(t *testing.T) {
}
}

type fixedKeychain map[name.Registry]Authenticator
type fixedKeychain map[Resource]Authenticator

var _ Keychain = (fixedKeychain)(nil)

// Resolve implements Keychain.
func (fk fixedKeychain) Resolve(reg name.Registry) (Authenticator, error) {
if auth, ok := fk[reg]; ok {
func (fk fixedKeychain) Resolve(target Resource) (Authenticator, error) {
if auth, ok := fk[target]; ok {
return auth, nil
}
return Anonymous, nil
Expand Down
5 changes: 2 additions & 3 deletions pkg/v1/google/keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"strings"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
)

// Keychain exports an instance of the google Keychain.
Expand Down Expand Up @@ -48,9 +47,9 @@ type googleKeychain struct{}
//
// In general, we don't worry about that here because we expect to use the same
// gcloud configuration in the scope of this one process.
func (gk *googleKeychain) Resolve(reg name.Registry) (authn.Authenticator, error) {
func (gk *googleKeychain) Resolve(target authn.Resource) (authn.Authenticator, error) {
// Only authenticate GCR so it works with authn.NewMultiKeychain to fallback.
if !strings.HasSuffix(reg.String(), "gcr.io") {
if !strings.HasSuffix(target.RegistryStr(), "gcr.io") {
return authn.Anonymous, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/v1/remote/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

// Delete removes the specified image reference from the remote registry.
func Delete(ref name.Reference, options ...Option) error {
o, err := makeOptions(ref.Context().Registry, options...)
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/v1/remote/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func Get(ref name.Reference, options ...Option) (*Descriptor, error) {
// Handle options and fetch the manifest with the acceptable MediaTypes in the
// Accept header.
func get(ref name.Reference, acceptable []types.MediaType, options ...Option) (*Descriptor, error) {
o, err := makeOptions(ref.Context().Registry, options...)
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/v1/remote/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (rl *remoteLayer) MediaType() (types.MediaType, error) {
// digest of the blob to be read and the repository portion is the repo where
// that blob lives.
func Layer(ref name.Digest, options ...Option) (v1.Layer, error) {
o, err := makeOptions(ref.Context().Registry, options...)
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/v1/remote/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type tags struct {
// List calls /tags/list for the given repository, returning the list of tags
// in the "tags" property.
func List(repo name.Repository, options ...Option) ([]string, error) {
o, err := makeOptions(repo.Registry, options...)
o, err := makeOptions(repo, options...)
if err != nil {
return nil, err
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/v1/remote/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/logs"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
)
Expand All @@ -34,7 +33,7 @@ type options struct {
platform v1.Platform
}

func makeOptions(reg name.Registry, opts ...Option) (*options, error) {
func makeOptions(target authn.Resource, opts ...Option) (*options, error) {
o := &options{
auth: authn.Anonymous,
transport: http.DefaultTransport,
Expand All @@ -48,7 +47,7 @@ func makeOptions(reg name.Registry, opts ...Option) (*options, error) {
}

if o.keychain != nil {
auth, err := o.keychain.Resolve(reg)
auth, err := o.keychain.Resolve(target)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/v1/remote/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func Write(ref name.Reference, img v1.Image, options ...Option) error {
return err
}

o, err := makeOptions(ref.Context().Registry, options...)
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return err
}
Expand Down Expand Up @@ -428,7 +428,7 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {
return err
}

o, err := makeOptions(ref.Context().Registry, options...)
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return err
}
Expand Down Expand Up @@ -484,7 +484,7 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {

// WriteLayer uploads the provided Layer to the specified name.Digest.
func WriteLayer(ref name.Digest, layer v1.Layer, options ...Option) error {
o, err := makeOptions(ref.Context().Registry, options...)
o, err := makeOptions(ref.Context(), options...)
if err != nil {
return err
}
Expand Down