mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
Move config and provider code out of pkg/credentialprovider and into staging.
This commit is contained in:
parent
0ced9d2854
commit
42fb89eb89
@ -59,6 +59,8 @@ import (
|
||||
"k8s.io/client-go/util/connrotation"
|
||||
"k8s.io/client-go/util/keyutil"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/component-base/cli/flag"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
"k8s.io/component-base/configz"
|
||||
"k8s.io/component-base/featuregate"
|
||||
@ -72,7 +74,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/capabilities"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet"
|
||||
kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
||||
@ -1116,7 +1117,7 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
|
||||
AllowPrivileged: true,
|
||||
})
|
||||
|
||||
credentialprovider.SetPreferredDockercfgPath(kubeServer.RootDirectory)
|
||||
credentialconfig.SetPreferredDockercfgPath(kubeServer.RootDirectory)
|
||||
klog.V(2).Infof("Using root directory: %v", kubeServer.RootDirectory)
|
||||
|
||||
if kubeDeps.OSInterface == nil {
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/component-base/version"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
@ -54,7 +55,7 @@ type ecrProvider struct {
|
||||
getterFactory tokenGetterFactory
|
||||
}
|
||||
|
||||
var _ credentialprovider.DockerConfigProvider = &ecrProvider{}
|
||||
var _ credentialconfig.DockerConfigProvider = &ecrProvider{}
|
||||
|
||||
func newECRProvider(getterFactory tokenGetterFactory) *ecrProvider {
|
||||
return &ecrProvider{
|
||||
@ -82,11 +83,11 @@ func (p *ecrProvider) Enabled() bool {
|
||||
|
||||
// Provide returns a DockerConfig with credentials from the cache if they are
|
||||
// found, or from ECR
|
||||
func (p *ecrProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
func (p *ecrProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
parsed, err := parseRepoURL(image)
|
||||
if err != nil {
|
||||
klog.V(3).Info(err)
|
||||
return credentialprovider.DockerConfig{}
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
||||
|
||||
if cfg, exists := p.getFromCache(parsed); exists {
|
||||
@ -98,15 +99,15 @@ func (p *ecrProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
cfg, err := p.getFromECR(parsed)
|
||||
if err != nil {
|
||||
klog.Errorf("error getting credentials from ECR for %s %v", parsed.registry, err)
|
||||
return credentialprovider.DockerConfig{}
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
||||
klog.V(3).Infof("Got ECR credentials from ECR API for %s", parsed.registry)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// getFromCache attempts to get credentials from the cache
|
||||
func (p *ecrProvider) getFromCache(parsed *parsedURL) (credentialprovider.DockerConfig, bool) {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
func (p *ecrProvider) getFromCache(parsed *parsedURL) (credentialconfig.DockerConfig, bool) {
|
||||
cfg := credentialconfig.DockerConfig{}
|
||||
|
||||
obj, exists, err := p.cache.GetByKey(parsed.registry)
|
||||
if err != nil {
|
||||
@ -124,8 +125,8 @@ func (p *ecrProvider) getFromCache(parsed *parsedURL) (credentialprovider.Docker
|
||||
}
|
||||
|
||||
// getFromECR gets credentials from ECR since they are not in the cache
|
||||
func (p *ecrProvider) getFromECR(parsed *parsedURL) (credentialprovider.DockerConfig, error) {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
func (p *ecrProvider) getFromECR(parsed *parsedURL) (credentialconfig.DockerConfig, error) {
|
||||
cfg := credentialconfig.DockerConfig{}
|
||||
getter, err := p.getterFactory.GetTokenGetterForRegion(parsed.region)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
@ -260,7 +261,7 @@ func (p *ecrTokenGetter) GetAuthorizationToken(input *ecr.GetAuthorizationTokenI
|
||||
|
||||
type cacheEntry struct {
|
||||
expiresAt time.Time
|
||||
credentials credentialprovider.DockerConfigEntry
|
||||
credentials credentialconfig.DockerConfigEntry
|
||||
registry string
|
||||
}
|
||||
|
||||
@ -275,7 +276,7 @@ func makeCacheEntry(data *ecr.AuthorizationData, registry string) (*cacheEntry,
|
||||
if len(parts) < 2 {
|
||||
return nil, errors.New("error getting username and password from authorization token")
|
||||
}
|
||||
creds := credentialprovider.DockerConfigEntry{
|
||||
creds := credentialconfig.DockerConfigEntry{
|
||||
Username: parts[0],
|
||||
Password: parts[1],
|
||||
Email: "not@val.id", // ECR doesn't care and Docker is about to obsolete it
|
||||
|
@ -35,6 +35,7 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
"k8s.io/legacy-cloud-providers/azure/auth"
|
||||
@ -65,7 +66,7 @@ func init() {
|
||||
|
||||
type cacheEntry struct {
|
||||
expiresAt time.Time
|
||||
credentials credentialprovider.DockerConfigEntry
|
||||
credentials credentialconfig.DockerConfigEntry
|
||||
registry string
|
||||
}
|
||||
|
||||
@ -122,7 +123,7 @@ func (az *azRegistriesClient) List(ctx context.Context) ([]containerregistry.Reg
|
||||
}
|
||||
|
||||
// NewACRProvider parses the specified configFile and returns a DockerConfigProvider
|
||||
func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider {
|
||||
func NewACRProvider(configFile *string) credentialconfig.DockerConfigProvider {
|
||||
return &acrProvider{
|
||||
file: configFile,
|
||||
cache: cache.NewExpirationStore(stringKeyFunc, &acrExpirationPolicy{}),
|
||||
@ -207,8 +208,8 @@ func (a *acrProvider) Enabled() bool {
|
||||
}
|
||||
|
||||
// getFromCache attempts to get credentials from the cache
|
||||
func (a *acrProvider) getFromCache(loginServer string) (credentialprovider.DockerConfig, bool) {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
func (a *acrProvider) getFromCache(loginServer string) (credentialconfig.DockerConfig, bool) {
|
||||
cfg := credentialconfig.DockerConfig{}
|
||||
obj, exists, err := a.cache.GetByKey(loginServer)
|
||||
if err != nil {
|
||||
klog.Errorf("error getting ACR credentials from cache: %v", err)
|
||||
@ -224,8 +225,8 @@ func (a *acrProvider) getFromCache(loginServer string) (credentialprovider.Docke
|
||||
}
|
||||
|
||||
// getFromACR gets credentials from ACR since they are not in the cache
|
||||
func (a *acrProvider) getFromACR(loginServer string) (credentialprovider.DockerConfig, error) {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
func (a *acrProvider) getFromACR(loginServer string) (credentialconfig.DockerConfig, error) {
|
||||
cfg := credentialconfig.DockerConfig{}
|
||||
cred, err := getACRDockerEntryFromARMToken(a, loginServer)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
@ -243,14 +244,14 @@ func (a *acrProvider) getFromACR(loginServer string) (credentialprovider.DockerC
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (a *acrProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
func (a *acrProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
loginServer := a.parseACRLoginServerFromImage(image)
|
||||
if loginServer == "" {
|
||||
klog.V(2).Infof("image(%s) is not from ACR, return empty authentication", image)
|
||||
return credentialprovider.DockerConfig{}
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
||||
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
cfg := credentialconfig.DockerConfig{}
|
||||
if a.config != nil && a.config.UseManagedIdentityExtension {
|
||||
var exists bool
|
||||
cfg, exists = a.getFromCache(loginServer)
|
||||
@ -267,7 +268,7 @@ func (a *acrProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
} else {
|
||||
// Add our entry for each of the supported container registry URLs
|
||||
for _, url := range containerRegistryUrls {
|
||||
cred := &credentialprovider.DockerConfigEntry{
|
||||
cred := &credentialconfig.DockerConfigEntry{
|
||||
Username: a.config.AADClientID,
|
||||
Password: a.config.AADClientSecret,
|
||||
Email: dummyRegistryEmail,
|
||||
@ -288,7 +289,7 @@ func (a *acrProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
}
|
||||
|
||||
if !hasBeenAdded {
|
||||
cred := &credentialprovider.DockerConfigEntry{
|
||||
cred := &credentialconfig.DockerConfigEntry{
|
||||
Username: a.config.AADClientID,
|
||||
Password: a.config.AADClientSecret,
|
||||
Email: dummyRegistryEmail,
|
||||
@ -299,7 +300,7 @@ func (a *acrProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
}
|
||||
|
||||
// add ACR anonymous repo support: use empty username and password for anonymous access
|
||||
defaultConfigEntry := credentialprovider.DockerConfigEntry{
|
||||
defaultConfigEntry := credentialconfig.DockerConfigEntry{
|
||||
Username: "",
|
||||
Password: "",
|
||||
Email: dummyRegistryEmail,
|
||||
@ -312,7 +313,7 @@ func getLoginServer(registry containerregistry.Registry) string {
|
||||
return *(*registry.RegistryProperties).LoginServer
|
||||
}
|
||||
|
||||
func getACRDockerEntryFromARMToken(a *acrProvider, loginServer string) (*credentialprovider.DockerConfigEntry, error) {
|
||||
func getACRDockerEntryFromARMToken(a *acrProvider, loginServer string) (*credentialconfig.DockerConfigEntry, error) {
|
||||
// Run EnsureFresh to make sure the token is valid and does not expire
|
||||
if err := a.servicePrincipalToken.EnsureFresh(); err != nil {
|
||||
klog.Errorf("Failed to ensure fresh service principal token: %v", err)
|
||||
@ -336,7 +337,7 @@ func getACRDockerEntryFromARMToken(a *acrProvider, loginServer string) (*credent
|
||||
}
|
||||
|
||||
klog.V(4).Infof("adding ACR docker config entry for: %s", loginServer)
|
||||
return &credentialprovider.DockerConfigEntry{
|
||||
return &credentialconfig.DockerConfigEntry{
|
||||
Username: dockerTokenLoginUsernameGUID,
|
||||
Password: registryRefreshToken,
|
||||
Email: dummyRegistryEmail,
|
||||
|
52
pkg/credentialprovider/default_provider.go
Normal file
52
pkg/credentialprovider/default_provider.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package credentialprovider
|
||||
|
||||
import (
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/klog/v2"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A DockerConfigProvider that simply reads the .dockercfg file
|
||||
type defaultDockerConfigProvider struct{}
|
||||
|
||||
// init registers our default provider, which simply reads the .dockercfg file.
|
||||
func init() {
|
||||
RegisterCredentialProvider(".dockercfg",
|
||||
&credentialconfig.CachingDockerConfigProvider{
|
||||
Provider: &defaultDockerConfigProvider{},
|
||||
Lifetime: 5 * time.Minute,
|
||||
})
|
||||
}
|
||||
|
||||
// Enabled implements dockerConfigProvider
|
||||
func (d *defaultDockerConfigProvider) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Provide implements dockerConfigProvider
|
||||
func (d *defaultDockerConfigProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
// Read the standard Docker credentials from .dockercfg
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFile(); err == nil {
|
||||
return cfg
|
||||
} else if !os.IsNotExist(err) {
|
||||
klog.V(4).Infof("Unable to parse Docker config file: %v", err)
|
||||
}
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
@ -17,70 +17,15 @@ limitations under the License.
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
"k8s.io/legacy-cloud-providers/gce/gcpcredential"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataURL = "http://metadata.google.internal./computeMetadata/v1/"
|
||||
metadataAttributes = metadataURL + "instance/attributes/"
|
||||
dockerConfigKey = metadataAttributes + "google-dockercfg"
|
||||
dockerConfigURLKey = metadataAttributes + "google-dockercfg-url"
|
||||
serviceAccounts = metadataURL + "instance/service-accounts/"
|
||||
metadataScopes = metadataURL + "instance/service-accounts/default/scopes"
|
||||
metadataToken = metadataURL + "instance/service-accounts/default/token"
|
||||
metadataEmail = metadataURL + "instance/service-accounts/default/email"
|
||||
storageScopePrefix = "https://www.googleapis.com/auth/devstorage"
|
||||
cloudPlatformScopePrefix = "https://www.googleapis.com/auth/cloud-platform"
|
||||
defaultServiceAccount = "default/"
|
||||
)
|
||||
|
||||
// Product file path that contains the cloud service name.
|
||||
// This is a variable instead of a const to enable testing.
|
||||
var gceProductNameFile = "/sys/class/dmi/id/product_name"
|
||||
|
||||
// For these urls, the parts of the host name can be glob, for example '*.gcr.io" will match
|
||||
// "foo.gcr.io" and "bar.gcr.io".
|
||||
var containerRegistryUrls = []string{"container.cloud.google.com", "gcr.io", "*.gcr.io", "*.pkg.dev"}
|
||||
|
||||
var metadataHeader = &http.Header{
|
||||
"Metadata-Flavor": []string{"Google"},
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from Google
|
||||
// Compute Engine metadata.
|
||||
type metadataProvider struct {
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from a specific
|
||||
// Google Compute Engine metadata key: 'google-dockercfg'.
|
||||
type dockerConfigKeyProvider struct {
|
||||
metadataProvider
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from a URL read from
|
||||
// a specific Google Compute Engine metadata key: 'google-dockercfg-url'.
|
||||
type dockerConfigURLKeyProvider struct {
|
||||
metadataProvider
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that provides a dockercfg with:
|
||||
// Username: "_token"
|
||||
// Password: "{access token from metadata}"
|
||||
type containerRegistryProvider struct {
|
||||
metadataProvider
|
||||
}
|
||||
|
||||
// init registers the various means by which credentials may
|
||||
// be resolved on GCP.
|
||||
func init() {
|
||||
@ -91,17 +36,17 @@ func init() {
|
||||
Timeout: metadataHTTPClientTimeout,
|
||||
}
|
||||
credentialprovider.RegisterCredentialProvider("google-dockercfg",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: &dockerConfigKeyProvider{
|
||||
metadataProvider{Client: httpClient},
|
||||
&credentialconfig.CachingDockerConfigProvider{
|
||||
Provider: &gcpcredential.DockerConfigKeyProvider{
|
||||
gcpcredential.MetadataProvider{Client: httpClient},
|
||||
},
|
||||
Lifetime: 60 * time.Second,
|
||||
})
|
||||
|
||||
credentialprovider.RegisterCredentialProvider("google-dockercfg-url",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: &dockerConfigURLKeyProvider{
|
||||
metadataProvider{Client: httpClient},
|
||||
&credentialconfig.CachingDockerConfigProvider{
|
||||
Provider: &gcpcredential.DockerConfigURLKeyProvider{
|
||||
gcpcredential.MetadataProvider{Client: httpClient},
|
||||
},
|
||||
Lifetime: 60 * time.Second,
|
||||
})
|
||||
@ -109,192 +54,7 @@ func init() {
|
||||
credentialprovider.RegisterCredentialProvider("google-container-registry",
|
||||
// Never cache this. The access token is already
|
||||
// cached by the metadata service.
|
||||
&containerRegistryProvider{
|
||||
metadataProvider{Client: httpClient},
|
||||
&gcpcredential.ContainerRegistryProvider{
|
||||
gcpcredential.MetadataProvider{Client: httpClient},
|
||||
})
|
||||
}
|
||||
|
||||
// Returns true if it finds a local GCE VM.
|
||||
// Looks at a product file that is an undocumented API.
|
||||
func onGCEVM() bool {
|
||||
var name string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
data, err := exec.Command("wmic", "computersystem", "get", "model").Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fields := strings.Split(strings.TrimSpace(string(data)), "\r\n")
|
||||
if len(fields) != 2 {
|
||||
klog.V(2).Infof("Received unexpected value retrieving system model: %q", string(data))
|
||||
return false
|
||||
}
|
||||
name = fields[1]
|
||||
} else {
|
||||
data, err := ioutil.ReadFile(gceProductNameFile)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Error while reading product_name: %v", err)
|
||||
return false
|
||||
}
|
||||
name = strings.TrimSpace(string(data))
|
||||
}
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Enabled implements DockerConfigProvider for all of the Google implementations.
|
||||
func (g *metadataProvider) Enabled() bool {
|
||||
return onGCEVM()
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *dockerConfigKeyProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
// Read the contents of the google-dockercfg metadata key and
|
||||
// parse them as an alternate .dockercfg
|
||||
if cfg, err := credentialprovider.ReadDockerConfigFileFromURL(dockerConfigKey, g.Client, metadataHeader); err != nil {
|
||||
klog.Errorf("while reading 'google-dockercfg' metadata: %v", err)
|
||||
} else {
|
||||
return cfg
|
||||
}
|
||||
|
||||
return credentialprovider.DockerConfig{}
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *dockerConfigURLKeyProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
// Read the contents of the google-dockercfg-url key and load a .dockercfg from there
|
||||
if url, err := credentialprovider.ReadURL(dockerConfigURLKey, g.Client, metadataHeader); err != nil {
|
||||
klog.Errorf("while reading 'google-dockercfg-url' metadata: %v", err)
|
||||
} else {
|
||||
if strings.HasPrefix(string(url), "http") {
|
||||
if cfg, err := credentialprovider.ReadDockerConfigFileFromURL(string(url), g.Client, nil); err != nil {
|
||||
klog.Errorf("while reading 'google-dockercfg-url'-specified url: %s, %v", string(url), err)
|
||||
} else {
|
||||
return cfg
|
||||
}
|
||||
} else {
|
||||
// TODO(mattmoor): support reading alternate scheme URLs (e.g. gs:// or s3://)
|
||||
klog.Errorf("Unsupported URL scheme: %s", string(url))
|
||||
}
|
||||
}
|
||||
|
||||
return credentialprovider.DockerConfig{}
|
||||
}
|
||||
|
||||
// runWithBackoff runs input function `f` with an exponential backoff.
|
||||
// Note that this method can block indefinitely.
|
||||
func runWithBackoff(f func() ([]byte, error)) []byte {
|
||||
var backoff = 100 * time.Millisecond
|
||||
const maxBackoff = time.Minute
|
||||
for {
|
||||
value, err := f()
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
backoff = backoff * 2
|
||||
if backoff > maxBackoff {
|
||||
backoff = maxBackoff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled implements a special metadata-based check, which verifies the
|
||||
// storage scope is available on the GCE VM.
|
||||
// If running on a GCE VM, check if 'default' service account exists.
|
||||
// If it does not exist, assume that registry is not enabled.
|
||||
// If default service account exists, check if relevant scopes exist in the default service account.
|
||||
// The metadata service can become temporarily inaccesible. Hence all requests to the metadata
|
||||
// service will be retried until the metadata server returns a `200`.
|
||||
// It is expected that "http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/" will return a `200`
|
||||
// and "http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/default/scopes" will also return `200`.
|
||||
// More information on metadata service can be found here - https://cloud.google.com/compute/docs/storing-retrieving-metadata
|
||||
func (g *containerRegistryProvider) Enabled() bool {
|
||||
if !onGCEVM() {
|
||||
return false
|
||||
}
|
||||
// Given that we are on GCE, we should keep retrying until the metadata server responds.
|
||||
value := runWithBackoff(func() ([]byte, error) {
|
||||
value, err := credentialprovider.ReadURL(serviceAccounts, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed to Get service accounts from gce metadata server: %v", err)
|
||||
}
|
||||
return value, err
|
||||
})
|
||||
// We expect the service account to return a list of account directories separated by newlines, e.g.,
|
||||
// sv-account-name1/
|
||||
// sv-account-name2/
|
||||
// ref: https://cloud.google.com/compute/docs/storing-retrieving-metadata
|
||||
defaultServiceAccountExists := false
|
||||
for _, sa := range strings.Split(string(value), "\n") {
|
||||
if strings.TrimSpace(sa) == defaultServiceAccount {
|
||||
defaultServiceAccountExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !defaultServiceAccountExists {
|
||||
klog.V(2).Infof("'default' service account does not exist. Found following service accounts: %q", string(value))
|
||||
return false
|
||||
}
|
||||
url := metadataScopes + "?alt=json"
|
||||
value = runWithBackoff(func() ([]byte, error) {
|
||||
value, err := credentialprovider.ReadURL(url, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed to Get scopes in default service account from gce metadata server: %v", err)
|
||||
}
|
||||
return value, err
|
||||
})
|
||||
var scopes []string
|
||||
if err := json.Unmarshal(value, &scopes); err != nil {
|
||||
klog.Errorf("Failed to unmarshal scopes: %v", err)
|
||||
return false
|
||||
}
|
||||
for _, v := range scopes {
|
||||
// cloudPlatformScope implies storage scope.
|
||||
if strings.HasPrefix(v, storageScopePrefix) || strings.HasPrefix(v, cloudPlatformScopePrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
klog.Warningf("Google container registry is disabled, no storage scope is available: %s", value)
|
||||
return false
|
||||
}
|
||||
|
||||
// tokenBlob is used to decode the JSON blob containing an access token
|
||||
// that is returned by GCE metadata.
|
||||
type tokenBlob struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *containerRegistryProvider) Provide(image string) credentialprovider.DockerConfig {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
|
||||
tokenJSONBlob, err := credentialprovider.ReadURL(metadataToken, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.Errorf("while reading access token endpoint: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
email, err := credentialprovider.ReadURL(metadataEmail, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.Errorf("while reading email endpoint: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
var parsedBlob tokenBlob
|
||||
if err := json.Unmarshal([]byte(tokenJSONBlob), &parsedBlob); err != nil {
|
||||
klog.Errorf("while parsing json blob %s: %v", tokenJSONBlob, err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
entry := credentialprovider.DockerConfigEntry{
|
||||
Username: "_token",
|
||||
Password: parsedBlob.AccessToken,
|
||||
Email: string(email),
|
||||
}
|
||||
|
||||
// Add our entry for each of the supported container registry URLs
|
||||
for _, k := range containerRegistryUrls {
|
||||
cfg[k] = entry
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
@ -30,7 +30,9 @@ import (
|
||||
"testing"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
"k8s.io/legacy-cloud-providers/gce/gcpcredential"
|
||||
)
|
||||
|
||||
func createProductNameFile() (string, error) {
|
||||
@ -55,17 +57,17 @@ func TestDockerKeyringFromGoogleDockerConfigMetadata(t *testing.T) {
|
||||
}`, registryURL, email, auth)
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
gcpcredential.GCEProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
defer os.Remove(gcpcredential.GCEProductNameFile)
|
||||
const probeEndpoint = "/computeMetadata/v1/"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Only serve the one metadata key.
|
||||
if probeEndpoint == r.URL.Path {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else if strings.HasSuffix(dockerConfigKey, r.URL.Path) {
|
||||
} else if strings.HasSuffix(gcpcredential.DockerConfigKey, r.URL.Path) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintln(w, sampleDockerConfig)
|
||||
@ -83,8 +85,8 @@ func TestDockerKeyringFromGoogleDockerConfigMetadata(t *testing.T) {
|
||||
})
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
provider := &dockerConfigKeyProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
provider := &gcpcredential.DockerConfigKeyProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if !provider.Enabled() {
|
||||
@ -128,11 +130,11 @@ func TestDockerKeyringFromGoogleDockerConfigMetadataUrl(t *testing.T) {
|
||||
}`, registryURL, email, auth)
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
gcpcredential.GCEProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
defer os.Remove(gcpcredential.GCEProductNameFile)
|
||||
const probeEndpoint = "/computeMetadata/v1/"
|
||||
const valueEndpoint = "/my/value"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -143,7 +145,7 @@ func TestDockerKeyringFromGoogleDockerConfigMetadataUrl(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintln(w, sampleDockerConfig)
|
||||
} else if strings.HasSuffix(dockerConfigURLKey, r.URL.Path) {
|
||||
} else if strings.HasSuffix(gcpcredential.DockerConfigURLKey, r.URL.Path) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/text")
|
||||
fmt.Fprint(w, "http://foo.bar.com"+valueEndpoint)
|
||||
@ -161,8 +163,8 @@ func TestDockerKeyringFromGoogleDockerConfigMetadataUrl(t *testing.T) {
|
||||
})
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
provider := &dockerConfigURLKeyProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
provider := &gcpcredential.DockerConfigURLKeyProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if !provider.Enabled() {
|
||||
@ -197,7 +199,7 @@ func TestContainerRegistryBasics(t *testing.T) {
|
||||
for _, registryURL := range registryURLs {
|
||||
t.Run(registryURL, func(t *testing.T) {
|
||||
email := "1234@project.gserviceaccount.com"
|
||||
token := &tokenBlob{AccessToken: "ya26.lots-of-indiscernible-garbage"} // Fake value for testing.
|
||||
token := &gcpcredential.TokenBlob{AccessToken: "ya26.lots-of-indiscernible-garbage"} // Fake value for testing.
|
||||
|
||||
const (
|
||||
serviceAccountsEndpoint = "/computeMetadata/v1/instance/service-accounts/"
|
||||
@ -207,18 +209,18 @@ func TestContainerRegistryBasics(t *testing.T) {
|
||||
tokenEndpoint = defaultEndpoint + "token"
|
||||
)
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
gcpcredential.GCEProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
defer os.Remove(gcpcredential.GCEProductNameFile)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Only serve the URL key and the value endpoint
|
||||
if scopeEndpoint == r.URL.Path {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `["%s.read_write"]`, storageScopePrefix)
|
||||
fmt.Fprintf(w, `["%s.read_write"]`, gcpcredential.StorageScopePrefix)
|
||||
} else if emailEndpoint == r.URL.Path {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, email)
|
||||
@ -247,8 +249,8 @@ func TestContainerRegistryBasics(t *testing.T) {
|
||||
})
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
provider := &containerRegistryProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
provider := &gcpcredential.ContainerRegistryProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if !provider.Enabled() {
|
||||
@ -301,11 +303,11 @@ func TestContainerRegistryNoServiceAccount(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
gcpcredential.GCEProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
defer os.Remove(gcpcredential.GCEProductNameFile)
|
||||
|
||||
// Make a transport that reroutes all traffic to the example server
|
||||
transport := utilnet.SetTransportDefaults(&http.Transport{
|
||||
@ -314,8 +316,8 @@ func TestContainerRegistryNoServiceAccount(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
provider := &containerRegistryProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
provider := &gcpcredential.ContainerRegistryProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if provider.Enabled() {
|
||||
@ -345,11 +347,11 @@ func TestContainerRegistryNoStorageScope(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
gcpcredential.GCEProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
defer os.Remove(gcpcredential.GCEProductNameFile)
|
||||
|
||||
// Make a transport that reroutes all traffic to the example server
|
||||
transport := utilnet.SetTransportDefaults(&http.Transport{
|
||||
@ -358,8 +360,8 @@ func TestContainerRegistryNoStorageScope(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
provider := &containerRegistryProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
provider := &gcpcredential.ContainerRegistryProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if provider.Enabled() {
|
||||
@ -390,11 +392,11 @@ func TestComputePlatformScopeSubstitutesStorageScope(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
gcpcredential.GCEProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
defer os.Remove(gcpcredential.GCEProductNameFile)
|
||||
|
||||
// Make a transport that reroutes all traffic to the example server
|
||||
transport := utilnet.SetTransportDefaults(&http.Transport{
|
||||
@ -403,8 +405,8 @@ func TestComputePlatformScopeSubstitutesStorageScope(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
provider := &containerRegistryProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
provider := &gcpcredential.ContainerRegistryProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if !provider.Enabled() {
|
||||
@ -425,15 +427,15 @@ func TestAllProvidersNoMetadata(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
providers := []credentialprovider.DockerConfigProvider{
|
||||
&dockerConfigKeyProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
providers := []credentialconfig.DockerConfigProvider{
|
||||
&gcpcredential.DockerConfigKeyProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
},
|
||||
&dockerConfigURLKeyProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
&gcpcredential.DockerConfigURLKeyProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
},
|
||||
&containerRegistryProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
&gcpcredential.ContainerRegistryProvider{
|
||||
gcpcredential.MetadataProvider{Client: &http.Client{Transport: transport}},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
)
|
||||
|
||||
// DockerKeyring tracks a set of docker registry credentials, maintaining a
|
||||
@ -48,7 +49,7 @@ type BasicDockerKeyring struct {
|
||||
// providersDockerKeyring is an implementation of DockerKeyring that
|
||||
// materializes its dockercfg based on a set of dockerConfigProviders.
|
||||
type providersDockerKeyring struct {
|
||||
Providers []DockerConfigProvider
|
||||
Providers []credentialconfig.DockerConfigProvider
|
||||
}
|
||||
|
||||
// AuthConfig contains authorization information for connecting to a Registry
|
||||
@ -74,7 +75,7 @@ type AuthConfig struct {
|
||||
}
|
||||
|
||||
// Add add some docker config in basic docker keyring
|
||||
func (dk *BasicDockerKeyring) Add(cfg DockerConfig) {
|
||||
func (dk *BasicDockerKeyring) Add(cfg credentialconfig.DockerConfig) {
|
||||
if dk.index == nil {
|
||||
dk.index = make([]string, 0)
|
||||
dk.creds = make(map[string][]AuthConfig)
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
)
|
||||
|
||||
func TestURLsMatch(t *testing.T) {
|
||||
@ -203,7 +205,7 @@ func TestDockerKeyringForGlob(t *testing.T) {
|
||||
}`, test.globURL, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
@ -271,7 +273,7 @@ func TestKeyringMiss(t *testing.T) {
|
||||
}`, test.globURL, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
@ -299,7 +301,7 @@ func TestKeyringMissWithDockerHubCredentials(t *testing.T) {
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
@ -325,7 +327,7 @@ func TestKeyringHitWithUnqualifiedDockerHub(t *testing.T) {
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
@ -366,7 +368,7 @@ func TestKeyringHitWithUnqualifiedLibraryDockerHub(t *testing.T) {
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
@ -407,7 +409,7 @@ func TestKeyringHitWithQualifiedDockerHub(t *testing.T) {
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
@ -464,9 +466,9 @@ func (d *testProvider) Enabled() bool {
|
||||
}
|
||||
|
||||
// Provide implements dockerConfigProvider
|
||||
func (d *testProvider) Provide(image string) DockerConfig {
|
||||
func (d *testProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
d.Count++
|
||||
return DockerConfig{}
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
||||
|
||||
func TestProvidersDockerKeyring(t *testing.T) {
|
||||
@ -474,7 +476,7 @@ func TestProvidersDockerKeyring(t *testing.T) {
|
||||
Count: 0,
|
||||
}
|
||||
keyring := &providersDockerKeyring{
|
||||
Providers: []DockerConfigProvider{
|
||||
Providers: []credentialconfig.DockerConfigProvider{
|
||||
provider,
|
||||
},
|
||||
}
|
||||
@ -510,13 +512,13 @@ func TestDockerKeyringLookup(t *testing.T) {
|
||||
}
|
||||
|
||||
dk := &BasicDockerKeyring{}
|
||||
dk.Add(DockerConfig{
|
||||
"bar.example.com/pong": DockerConfigEntry{
|
||||
dk.Add(credentialconfig.DockerConfig{
|
||||
"bar.example.com/pong": credentialconfig.DockerConfigEntry{
|
||||
Username: grace.Username,
|
||||
Password: grace.Password,
|
||||
Email: grace.Email,
|
||||
},
|
||||
"bar.example.com": DockerConfigEntry{
|
||||
"bar.example.com": credentialconfig.DockerConfigEntry{
|
||||
Username: ada.Username,
|
||||
Password: ada.Password,
|
||||
Email: ada.Email,
|
||||
@ -571,8 +573,8 @@ func TestIssue3797(t *testing.T) {
|
||||
}
|
||||
|
||||
dk := &BasicDockerKeyring{}
|
||||
dk.Add(DockerConfig{
|
||||
"https://quay.io/v1/": DockerConfigEntry{
|
||||
dk.Add(credentialconfig.DockerConfig{
|
||||
"https://quay.io/v1/": credentialconfig.DockerConfigEntry{
|
||||
Username: rex.Username,
|
||||
Password: rex.Password,
|
||||
Email: rex.Email,
|
||||
|
@ -21,19 +21,20 @@ import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// All registered credential providers.
|
||||
var providersMutex sync.Mutex
|
||||
var providers = make(map[string]DockerConfigProvider)
|
||||
var providers = make(map[string]credentialconfig.DockerConfigProvider)
|
||||
|
||||
// RegisterCredentialProvider is called by provider implementations on
|
||||
// initialization to register themselves, like so:
|
||||
// func init() {
|
||||
// RegisterCredentialProvider("name", &myProvider{...})
|
||||
// }
|
||||
func RegisterCredentialProvider(name string, provider DockerConfigProvider) {
|
||||
func RegisterCredentialProvider(name string, provider credentialconfig.DockerConfigProvider) {
|
||||
providersMutex.Lock()
|
||||
defer providersMutex.Unlock()
|
||||
_, found := providers[name]
|
||||
@ -48,7 +49,7 @@ func RegisterCredentialProvider(name string, provider DockerConfigProvider) {
|
||||
// which draws from the set of registered credential providers.
|
||||
func NewDockerKeyring() DockerKeyring {
|
||||
keyring := &providersDockerKeyring{
|
||||
Providers: make([]DockerConfigProvider, 0),
|
||||
Providers: make([]credentialconfig.DockerConfigProvider, 0),
|
||||
}
|
||||
|
||||
keys := reflect.ValueOf(providers).MapKeys()
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
@ -27,17 +28,17 @@ import (
|
||||
// then a DockerKeyring is built based on every hit and unioned with the defaultKeyring.
|
||||
// If they do not, then the default keyring is returned
|
||||
func MakeDockerKeyring(passedSecrets []v1.Secret, defaultKeyring credentialprovider.DockerKeyring) (credentialprovider.DockerKeyring, error) {
|
||||
passedCredentials := []credentialprovider.DockerConfig{}
|
||||
passedCredentials := []credentialconfig.DockerConfig{}
|
||||
for _, passedSecret := range passedSecrets {
|
||||
if dockerConfigJSONBytes, dockerConfigJSONExists := passedSecret.Data[v1.DockerConfigJsonKey]; (passedSecret.Type == v1.SecretTypeDockerConfigJson) && dockerConfigJSONExists && (len(dockerConfigJSONBytes) > 0) {
|
||||
dockerConfigJSON := credentialprovider.DockerConfigJSON{}
|
||||
dockerConfigJSON := credentialconfig.DockerConfigJSON{}
|
||||
if err := json.Unmarshal(dockerConfigJSONBytes, &dockerConfigJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
passedCredentials = append(passedCredentials, dockerConfigJSON.Auths)
|
||||
} else if dockercfgBytes, dockercfgExists := passedSecret.Data[v1.DockerConfigKey]; (passedSecret.Type == v1.SecretTypeDockercfg) && dockercfgExists && (len(dockercfgBytes) > 0) {
|
||||
dockercfg := credentialprovider.DockerConfig{}
|
||||
dockercfg := credentialconfig.DockerConfig{}
|
||||
if err := json.Unmarshal(dockercfgBytes, &dockercfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package credentialprovider
|
||||
package credentialconfig
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
@ -116,7 +116,7 @@ func ReadDockercfgFile(searchPaths []string) (cfg DockerConfig, err error) {
|
||||
klog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
|
||||
continue
|
||||
}
|
||||
cfg, err := readDockerConfigFileFromBytes(contents)
|
||||
cfg, err := ReadDockerConfigFileFromBytes(contents)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("couldn't get the config from %q contents: %v", absDockerConfigFileLocation, err)
|
||||
continue
|
||||
@ -226,13 +226,13 @@ func ReadURL(url string, client *http.Client, header *http.Header) (body []byte,
|
||||
// ReadDockerConfigFileFromURL read a docker config file from the given url
|
||||
func ReadDockerConfigFileFromURL(url string, client *http.Client, header *http.Header) (cfg DockerConfig, err error) {
|
||||
if contents, err := ReadURL(url, client, header); err == nil {
|
||||
return readDockerConfigFileFromBytes(contents)
|
||||
return ReadDockerConfigFileFromBytes(contents)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func readDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
|
||||
func ReadDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
|
||||
if err = json.Unmarshal(contents, &cfg); err != nil {
|
||||
return nil, errors.New("error occurred while trying to unmarshal json")
|
||||
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package credentialprovider
|
||||
package credentialconfig
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
@ -338,7 +338,7 @@ func TestReadDockerConfigFileFromBytes(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cfg, err := readDockerConfigFileFromBytes(tc.input)
|
||||
cfg, err := ReadDockerConfigFileFromBytes(tc.input)
|
||||
if err != nil && !tc.errorExpected {
|
||||
t.Fatalf("Error was not expected: %v", err)
|
||||
}
|
@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package credentialprovider
|
||||
package credentialconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
@ -39,18 +38,6 @@ type DockerConfigProvider interface {
|
||||
Provide(image string) DockerConfig
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that simply reads the .dockercfg file
|
||||
type defaultDockerConfigProvider struct{}
|
||||
|
||||
// init registers our default provider, which simply reads the .dockercfg file.
|
||||
func init() {
|
||||
RegisterCredentialProvider(".dockercfg",
|
||||
&CachingDockerConfigProvider{
|
||||
Provider: &defaultDockerConfigProvider{},
|
||||
Lifetime: 5 * time.Minute,
|
||||
})
|
||||
}
|
||||
|
||||
// CachingDockerConfigProvider implements DockerConfigProvider by composing
|
||||
// with another DockerConfigProvider and caching the DockerConfig it provides
|
||||
// for a pre-specified lifetime.
|
||||
@ -68,22 +55,6 @@ type CachingDockerConfigProvider struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Enabled implements dockerConfigProvider
|
||||
func (d *defaultDockerConfigProvider) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Provide implements dockerConfigProvider
|
||||
func (d *defaultDockerConfigProvider) Provide(image string) DockerConfig {
|
||||
// Read the standard Docker credentials from .dockercfg
|
||||
if cfg, err := ReadDockerConfigFile(); err == nil {
|
||||
return cfg
|
||||
} else if !os.IsNotExist(err) {
|
||||
klog.V(4).Infof("Unable to parse Docker config file: %v", err)
|
||||
}
|
||||
return DockerConfig{}
|
||||
}
|
||||
|
||||
// Enabled implements dockerConfigProvider
|
||||
func (d *CachingDockerConfigProvider) Enabled() bool {
|
||||
return d.Provider.Enabled()
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package credentialprovider
|
||||
package credentialconfig
|
||||
|
||||
import (
|
||||
"testing"
|
@ -0,0 +1,266 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gcpcredential
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/cloud-provider/credentialconfig"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataURL = "http://metadata.google.internal./computeMetadata/v1/"
|
||||
metadataAttributes = metadataURL + "instance/attributes/"
|
||||
DockerConfigKey = metadataAttributes + "google-dockercfg"
|
||||
DockerConfigURLKey = metadataAttributes + "google-dockercfg-url"
|
||||
serviceAccounts = metadataURL + "instance/service-accounts/"
|
||||
metadataScopes = metadataURL + "instance/service-accounts/default/scopes"
|
||||
metadataToken = metadataURL + "instance/service-accounts/default/token"
|
||||
metadataEmail = metadataURL + "instance/service-accounts/default/email"
|
||||
StorageScopePrefix = "https://www.googleapis.com/auth/devstorage"
|
||||
cloudPlatformScopePrefix = "https://www.googleapis.com/auth/cloud-platform"
|
||||
defaultServiceAccount = "default/"
|
||||
)
|
||||
|
||||
// Product file path that contains the cloud service name.
|
||||
// This is a variable instead of a const to enable testing.
|
||||
var GCEProductNameFile = "/sys/class/dmi/id/product_name"
|
||||
|
||||
// For these urls, the parts of the host name can be glob, for example '*.gcr.io" will match
|
||||
// "foo.gcr.io" and "bar.gcr.io".
|
||||
var containerRegistryUrls = []string{"container.cloud.google.com", "gcr.io", "*.gcr.io", "*.pkg.dev"}
|
||||
|
||||
var metadataHeader = &http.Header{
|
||||
"Metadata-Flavor": []string{"Google"},
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from Google
|
||||
// Compute Engine metadata.
|
||||
type MetadataProvider struct {
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from a specific
|
||||
// Google Compute Engine metadata key: 'google-dockercfg'.
|
||||
type DockerConfigKeyProvider struct {
|
||||
MetadataProvider
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from a URL read from
|
||||
// a specific Google Compute Engine metadata key: 'google-dockercfg-url'.
|
||||
type DockerConfigURLKeyProvider struct {
|
||||
MetadataProvider
|
||||
}
|
||||
|
||||
// A DockerConfigProvider that provides a dockercfg with:
|
||||
// Username: "_token"
|
||||
// Password: "{access token from metadata}"
|
||||
type ContainerRegistryProvider struct {
|
||||
MetadataProvider
|
||||
}
|
||||
|
||||
// Returns true if it finds a local GCE VM.
|
||||
// Looks at a product file that is an undocumented API.
|
||||
func onGCEVM() bool {
|
||||
var name string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
data, err := exec.Command("wmic", "computersystem", "get", "model").Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fields := strings.Split(strings.TrimSpace(string(data)), "\r\n")
|
||||
if len(fields) != 2 {
|
||||
klog.V(2).Infof("Received unexpected value retrieving system model: %q", string(data))
|
||||
return false
|
||||
}
|
||||
name = fields[1]
|
||||
} else {
|
||||
data, err := ioutil.ReadFile(GCEProductNameFile)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Error while reading product_name: %v", err)
|
||||
return false
|
||||
}
|
||||
name = strings.TrimSpace(string(data))
|
||||
}
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Enabled implements DockerConfigProvider for all of the Google implementations.
|
||||
func (g *MetadataProvider) Enabled() bool {
|
||||
return onGCEVM()
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *DockerConfigKeyProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
// Read the contents of the google-dockercfg metadata key and
|
||||
// parse them as an alternate .dockercfg
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromURL(DockerConfigKey, g.Client, metadataHeader); err != nil {
|
||||
klog.Errorf("while reading 'google-dockercfg' metadata: %v", err)
|
||||
} else {
|
||||
return cfg
|
||||
}
|
||||
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *DockerConfigURLKeyProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
// Read the contents of the google-dockercfg-url key and load a .dockercfg from there
|
||||
if url, err := credentialconfig.ReadURL(DockerConfigURLKey, g.Client, metadataHeader); err != nil {
|
||||
klog.Errorf("while reading 'google-dockercfg-url' metadata: %v", err)
|
||||
} else {
|
||||
if strings.HasPrefix(string(url), "http") {
|
||||
if cfg, err := credentialconfig.ReadDockerConfigFileFromURL(string(url), g.Client, nil); err != nil {
|
||||
klog.Errorf("while reading 'google-dockercfg-url'-specified url: %s, %v", string(url), err)
|
||||
} else {
|
||||
return cfg
|
||||
}
|
||||
} else {
|
||||
// TODO(mattmoor): support reading alternate scheme URLs (e.g. gs:// or s3://)
|
||||
klog.Errorf("Unsupported URL scheme: %s", string(url))
|
||||
}
|
||||
}
|
||||
|
||||
return credentialconfig.DockerConfig{}
|
||||
}
|
||||
|
||||
// runWithBackoff runs input function `f` with an exponential backoff.
|
||||
// Note that this method can block indefinitely.
|
||||
func runWithBackoff(f func() ([]byte, error)) []byte {
|
||||
var backoff = 100 * time.Millisecond
|
||||
const maxBackoff = time.Minute
|
||||
for {
|
||||
value, err := f()
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
backoff = backoff * 2
|
||||
if backoff > maxBackoff {
|
||||
backoff = maxBackoff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled implements a special metadata-based check, which verifies the
|
||||
// storage scope is available on the GCE VM.
|
||||
// If running on a GCE VM, check if 'default' service account exists.
|
||||
// If it does not exist, assume that registry is not enabled.
|
||||
// If default service account exists, check if relevant scopes exist in the default service account.
|
||||
// The metadata service can become temporarily inaccesible. Hence all requests to the metadata
|
||||
// service will be retried until the metadata server returns a `200`.
|
||||
// It is expected that "http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/" will return a `200`
|
||||
// and "http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/default/scopes" will also return `200`.
|
||||
// More information on metadata service can be found here - https://cloud.google.com/compute/docs/storing-retrieving-metadata
|
||||
func (g *ContainerRegistryProvider) Enabled() bool {
|
||||
if !onGCEVM() {
|
||||
return false
|
||||
}
|
||||
// Given that we are on GCE, we should keep retrying until the metadata server responds.
|
||||
value := runWithBackoff(func() ([]byte, error) {
|
||||
value, err := credentialconfig.ReadURL(serviceAccounts, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed to Get service accounts from gce metadata server: %v", err)
|
||||
}
|
||||
return value, err
|
||||
})
|
||||
// We expect the service account to return a list of account directories separated by newlines, e.g.,
|
||||
// sv-account-name1/
|
||||
// sv-account-name2/
|
||||
// ref: https://cloud.google.com/compute/docs/storing-retrieving-metadata
|
||||
defaultServiceAccountExists := false
|
||||
for _, sa := range strings.Split(string(value), "\n") {
|
||||
if strings.TrimSpace(sa) == defaultServiceAccount {
|
||||
defaultServiceAccountExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !defaultServiceAccountExists {
|
||||
klog.V(2).Infof("'default' service account does not exist. Found following service accounts: %q", string(value))
|
||||
return false
|
||||
}
|
||||
url := metadataScopes + "?alt=json"
|
||||
value = runWithBackoff(func() ([]byte, error) {
|
||||
value, err := credentialconfig.ReadURL(url, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed to Get scopes in default service account from gce metadata server: %v", err)
|
||||
}
|
||||
return value, err
|
||||
})
|
||||
var scopes []string
|
||||
if err := json.Unmarshal(value, &scopes); err != nil {
|
||||
klog.Errorf("Failed to unmarshal scopes: %v", err)
|
||||
return false
|
||||
}
|
||||
for _, v := range scopes {
|
||||
// cloudPlatformScope implies storage scope.
|
||||
if strings.HasPrefix(v, StorageScopePrefix) || strings.HasPrefix(v, cloudPlatformScopePrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
klog.Warningf("Google container registry is disabled, no storage scope is available: %s", value)
|
||||
return false
|
||||
}
|
||||
|
||||
// TokenBlob is used to decode the JSON blob containing an access token
|
||||
// that is returned by GCE metadata.
|
||||
type TokenBlob struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *ContainerRegistryProvider) Provide(image string) credentialconfig.DockerConfig {
|
||||
cfg := credentialconfig.DockerConfig{}
|
||||
|
||||
tokenJSONBlob, err := credentialconfig.ReadURL(metadataToken, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.Errorf("while reading access token endpoint: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
email, err := credentialconfig.ReadURL(metadataEmail, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
klog.Errorf("while reading email endpoint: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
var parsedBlob TokenBlob
|
||||
if err := json.Unmarshal([]byte(tokenJSONBlob), &parsedBlob); err != nil {
|
||||
klog.Errorf("while parsing json blob %s: %v", tokenJSONBlob, err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
entry := credentialconfig.DockerConfigEntry{
|
||||
Username: "_token",
|
||||
Password: parsedBlob.AccessToken,
|
||||
Email: string(email),
|
||||
}
|
||||
|
||||
// Add our entry for each of the supported container registry URLs
|
||||
for _, k := range containerRegistryUrls {
|
||||
cfg[k] = entry
|
||||
}
|
||||
return cfg
|
||||
}
|
Loading…
Reference in New Issue
Block a user