Skip to content

Commit

Permalink
Add pkg/authn/kubernetes
Browse files Browse the repository at this point in the history
This takes the Kubernetes client-go parts from k8schain, removes the
forked-upstream credentialprovider bits and rewrites them to include
only exactly what we need.

It also reimplements k8schain in terms of this new K8s helper, and of
wrapped cred helpers using authn.NewKeychainFromHelper.
  • Loading branch information
imjasonh committed Jan 6, 2022
1 parent d9bfbcb commit 6f79f33
Show file tree
Hide file tree
Showing 12 changed files with 1,842 additions and 241 deletions.
3 changes: 3 additions & 0 deletions hack/presubmit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ go test ./...

pushd ${PROJECT_ROOT}/pkg/authn/k8schain
trap popd EXIT
go build ./...

pushd ${PROJECT_ROOT}/pkg/authn/kubernetes
trap popd EXIT
go test ./...
2 changes: 1 addition & 1 deletion pkg/authn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import (

func main() {
// ...
ecrHelper := ecr.ECRHelper{ClientFactory: api.DefaultClientFactory()}
ecrHelper := ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}
img, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.NewKeychainFromHelper(ecrHelper)))
if err != nil {
panic(err)
Expand Down
17 changes: 11 additions & 6 deletions pkg/authn/k8schain/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ module github.com/google/go-containerregistry/pkg/authn/k8schain
go 1.14

require (
github.com/google/go-containerregistry v0.5.2-0.20210609162550-f0ce2270b3b4
github.com/vdemeester/k8s-pkg-credentialprovider v1.21.0-1
k8s.io/api v0.21.1
k8s.io/apimachinery v0.21.1
k8s.io/client-go v0.21.1
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20211027214941-f15886b5ccdc
github.com/chrismellard/docker-credential-acr-env v0.0.0-20210203204924-09e2b5a8ac86
github.com/google/go-containerregistry v0.8.0
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-00010101000000-000000000000
k8s.io/api v0.23.1
k8s.io/apimachinery v0.23.1
k8s.io/client-go v0.23.1
)

replace github.com/google/go-containerregistry => ../../..
replace (
github.com/google/go-containerregistry => ../../../
github.com/google/go-containerregistry/pkg/authn/kubernetes => ../kubernetes/
)
240 changes: 179 additions & 61 deletions pkg/authn/k8schain/go.sum

Large diffs are not rendered by default.

149 changes: 46 additions & 103 deletions pkg/authn/k8schain/k8schain.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ package k8schain

import (
"context"
"fmt"
"sync"

ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/awslabs/amazon-ecr-credential-helper/ecr-login/api"
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
credentialprovider "github.com/vdemeester/k8s-pkg-credentialprovider"
credentialprovidersecrets "github.com/vdemeester/k8s-pkg-credentialprovider/secrets"
kauth "github.com/google/go-containerregistry/pkg/authn/kubernetes"
"github.com/google/go-containerregistry/pkg/v1/google"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

var (
amazonKeychain authn.Keychain = authn.NewKeychainFromHelper(ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}})
azureKeychain authn.Keychain = authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper())
)

// Options holds configuration data for guiding credential resolution.
type Options struct {
// Namespace holds the namespace inside of which we are resolving the
Expand All @@ -42,60 +46,35 @@ type Options struct {
ImagePullSecrets []string
}

var (
keyring credentialprovider.DockerKeyring
once sync.Once
)

// New returns a new authn.Keychain suitable for resolving image references as
// scoped by the provided Options. It speaks to Kubernetes through the provided
// client interface.
//
// Deprecated: Use pkg/authn/kubernetes.
func New(ctx context.Context, client kubernetes.Interface, opt Options) (authn.Keychain, error) {
if opt.Namespace == "" {
opt.Namespace = "default"
}
if opt.ServiceAccountName == "" {
opt.ServiceAccountName = "default"
}

// Implement a Kubernetes-style authentication keychain.
// This needs to support roughly the following kinds of authentication:
// 1) The implicit authentication from k8s.io/kubernetes/pkg/credentialprovider
// 2) The explicit authentication from imagePullSecrets on Pod
// 3) The semi-implicit authentication where imagePullSecrets are on the
// Pod's service account.

// First, fetch all of the explicitly declared pull secrets
var pullSecrets []corev1.Secret
if client != nil {
for _, name := range opt.ImagePullSecrets {
ps, err := client.CoreV1().Secrets(opt.Namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
pullSecrets = append(pullSecrets, *ps)
}

// Second, fetch all of the pull secrets attached to our service account.
sa, err := client.CoreV1().ServiceAccounts(opt.Namespace).Get(ctx, opt.ServiceAccountName, metav1.GetOptions{})
if err != nil {
return nil, err
}
for _, localObj := range sa.ImagePullSecrets {
ps, err := client.CoreV1().Secrets(opt.Namespace).Get(ctx, localObj.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
pullSecrets = append(pullSecrets, *ps)
}
k8s, err := kauth.New(ctx, client, kauth.Options{
Namespace: opt.Namespace,
ServiceAccountName: opt.ServiceAccountName,
ImagePullSecrets: opt.ImagePullSecrets,
})
if err != nil {
return nil, err
}

return NewFromPullSecrets(ctx, pullSecrets)
return authn.NewMultiKeychain(
authn.DefaultKeychain,
google.Keychain,
amazonKeychain,
azureKeychain,
k8s,
), nil
}

// NewInCluster returns a new authn.Keychain suitable for resolving image references as
// scoped by the provided Options, constructing a kubernetes.Interface based on in-cluster
// authentication.
//
// Deprecated: Use pkg/authn/kubernetes.
func NewInCluster(ctx context.Context, opt Options) (authn.Keychain, error) {
clusterConfig, err := rest.InClusterConfig()
if err != nil {
Expand All @@ -117,68 +96,32 @@ func NewInCluster(ctx context.Context, opt Options) (authn.Keychain, error) {
// for Kubernetes authentication, but this actually targets a different use-case. What
// remains is an interesting sweet spot: this variant can serve as a credential provider
// for all of the major public clouds, but in library form (vs. an executable you exec).
//
// Deprecated: Use pkg/authn/{amazon,azure,google}.Keychain.
func NewNoClient(ctx context.Context) (authn.Keychain, error) {
return New(ctx, nil, Options{})
return authn.NewMultiKeychain(
authn.DefaultKeychain,
google.Keychain,
amazonKeychain,
azureKeychain,
), nil
}

// NewFromPullSecrets returns a new authn.Keychain suitable for resolving image references as
// scoped by the pull secrets.
//
// Deprecated: Use pkg/authn/kubernetes.
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)
k8s, err := kauth.NewFromPullSecrets(ctx, pullSecrets)
if err != nil {
return nil, err
}
return &keychain{
keyring: kr,
}, nil
}

type lazyProvider struct {
kc *keychain
image string
}

// Authorization implements Authenticator.
func (lp lazyProvider) Authorization() (*authn.AuthConfig, error) {
creds, found := lp.kc.keyring.Lookup(lp.image)
if !found || len(creds) < 1 {
return nil, fmt.Errorf("keychain returned no credentials for %q", lp.image)
}
authConfig := creds[0]
return &authn.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
}, nil
}

type keychain struct {
keyring credentialprovider.DockerKeyring
}

// Resolve implements authn.Keychain
func (kc *keychain) Resolve(target authn.Resource) (authn.Authenticator, error) {
var image string
if repo, ok := target.(name.Repository); ok {
image = repo.String()
} else {
// Lookup expects an image reference and we only have a registry.
image = target.RegistryStr() + "/foo/bar"
}

if creds, found := kc.keyring.Lookup(image); !found || len(creds) < 1 {
return authn.Anonymous, nil
}
// TODO(mattmoor): How to support multiple credentials?
return lazyProvider{
kc: kc,
image: image,
}, nil
return authn.NewMultiKeychain(
authn.DefaultKeychain,
google.Keychain,
amazonKeychain,
azureKeychain,
k8s,
), nil
}
23 changes: 0 additions & 23 deletions pkg/authn/k8schain/k8schain_aws.go

This file was deleted.

23 changes: 0 additions & 23 deletions pkg/authn/k8schain/k8schain_azure.go

This file was deleted.

23 changes: 0 additions & 23 deletions pkg/authn/k8schain/k8schain_gcp.go

This file was deleted.

47 changes: 47 additions & 0 deletions pkg/authn/kubernetes/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module github.com/google/go-containerregistry/pkg/authn/kubernetes

go 1.17

require (
github.com/google/go-containerregistry v0.8.0
k8s.io/api v0.23.1
k8s.io/apimachinery v0.23.1
k8s.io/client-go v0.23.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.12+incompatible // indirect
github.com/docker/docker v20.10.12+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
Loading

0 comments on commit 6f79f33

Please sign in to comment.