mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-17 23:19:26 +00:00
Move config and provider code out of pkg/credentialprovider and into staging.
This commit is contained in:
@@ -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,
|
||||
|
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
maxReadLength = 10 * 1 << 20 // 10MB
|
||||
)
|
||||
|
||||
// DockerConfigJSON represents ~/.docker/config.json file info
|
||||
// see https://github.com/docker/docker/pull/12009
|
||||
type DockerConfigJSON struct {
|
||||
Auths DockerConfig `json:"auths"`
|
||||
// +optional
|
||||
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
|
||||
}
|
||||
|
||||
// DockerConfig represents the config file used by the docker CLI.
|
||||
// This config that represents the credentials that should be used
|
||||
// when pulling images from specific image repositories.
|
||||
type DockerConfig map[string]DockerConfigEntry
|
||||
|
||||
// DockerConfigEntry wraps a docker config as a entry
|
||||
type DockerConfigEntry struct {
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
Provider DockerConfigProvider
|
||||
}
|
||||
|
||||
var (
|
||||
preferredPathLock sync.Mutex
|
||||
preferredPath = ""
|
||||
workingDirPath = ""
|
||||
homeDirPath, _ = os.UserHomeDir()
|
||||
rootDirPath = "/"
|
||||
homeJSONDirPath = filepath.Join(homeDirPath, ".docker")
|
||||
rootJSONDirPath = filepath.Join(rootDirPath, ".docker")
|
||||
|
||||
configFileName = ".dockercfg"
|
||||
configJSONFileName = "config.json"
|
||||
)
|
||||
|
||||
// SetPreferredDockercfgPath set preferred docker config path
|
||||
func SetPreferredDockercfgPath(path string) {
|
||||
preferredPathLock.Lock()
|
||||
defer preferredPathLock.Unlock()
|
||||
preferredPath = path
|
||||
}
|
||||
|
||||
// GetPreferredDockercfgPath get preferred docker config path
|
||||
func GetPreferredDockercfgPath() string {
|
||||
preferredPathLock.Lock()
|
||||
defer preferredPathLock.Unlock()
|
||||
return preferredPath
|
||||
}
|
||||
|
||||
//DefaultDockercfgPaths returns default search paths of .dockercfg
|
||||
func DefaultDockercfgPaths() []string {
|
||||
return []string{GetPreferredDockercfgPath(), workingDirPath, homeDirPath, rootDirPath}
|
||||
}
|
||||
|
||||
//DefaultDockerConfigJSONPaths returns default search paths of .docker/config.json
|
||||
func DefaultDockerConfigJSONPaths() []string {
|
||||
return []string{GetPreferredDockercfgPath(), workingDirPath, homeJSONDirPath, rootJSONDirPath}
|
||||
}
|
||||
|
||||
// ReadDockercfgFile attempts to read a legacy dockercfg file from the given paths.
|
||||
// if searchPaths is empty, the default paths are used.
|
||||
func ReadDockercfgFile(searchPaths []string) (cfg DockerConfig, err error) {
|
||||
if len(searchPaths) == 0 {
|
||||
searchPaths = DefaultDockercfgPaths()
|
||||
}
|
||||
|
||||
for _, configPath := range searchPaths {
|
||||
absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configFileName))
|
||||
if err != nil {
|
||||
klog.Errorf("while trying to canonicalize %s: %v", configPath, err)
|
||||
continue
|
||||
}
|
||||
klog.V(4).Infof("looking for .dockercfg at %s", absDockerConfigFileLocation)
|
||||
contents, err := ioutil.ReadFile(absDockerConfigFileLocation)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
klog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
|
||||
continue
|
||||
}
|
||||
cfg, err := readDockerConfigFileFromBytes(contents)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("couldn't get the config from %q contents: %v", absDockerConfigFileLocation, err)
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(4).Infof("found .dockercfg at %s", absDockerConfigFileLocation)
|
||||
return cfg, nil
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("couldn't find valid .dockercfg after checking in %v", searchPaths)
|
||||
}
|
||||
|
||||
// ReadDockerConfigJSONFile attempts to read a docker config.json file from the given paths.
|
||||
// if searchPaths is empty, the default paths are used.
|
||||
func ReadDockerConfigJSONFile(searchPaths []string) (cfg DockerConfig, err error) {
|
||||
if len(searchPaths) == 0 {
|
||||
searchPaths = DefaultDockerConfigJSONPaths()
|
||||
}
|
||||
for _, configPath := range searchPaths {
|
||||
absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configJSONFileName))
|
||||
if err != nil {
|
||||
klog.Errorf("while trying to canonicalize %s: %v", configPath, err)
|
||||
continue
|
||||
}
|
||||
klog.V(4).Infof("looking for %s at %s", configJSONFileName, absDockerConfigFileLocation)
|
||||
cfg, err = ReadSpecificDockerConfigJSONFile(absDockerConfigFileLocation)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
klog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
klog.V(4).Infof("found valid %s at %s", configJSONFileName, absDockerConfigFileLocation)
|
||||
return cfg, nil
|
||||
}
|
||||
return nil, fmt.Errorf("couldn't find valid %s after checking in %v", configJSONFileName, searchPaths)
|
||||
|
||||
}
|
||||
|
||||
//ReadSpecificDockerConfigJSONFile attempts to read docker configJSON from a given file path.
|
||||
func ReadSpecificDockerConfigJSONFile(filePath string) (cfg DockerConfig, err error) {
|
||||
var contents []byte
|
||||
|
||||
if contents, err = ioutil.ReadFile(filePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readDockerConfigJSONFileFromBytes(contents)
|
||||
}
|
||||
|
||||
// ReadDockerConfigFile read a docker config file from default path
|
||||
func ReadDockerConfigFile() (cfg DockerConfig, err error) {
|
||||
if cfg, err := ReadDockerConfigJSONFile(nil); err == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
// Can't find latest config file so check for the old one
|
||||
return ReadDockercfgFile(nil)
|
||||
}
|
||||
|
||||
// HTTPError wraps a non-StatusOK error code as an error.
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
URL string
|
||||
}
|
||||
|
||||
// Error implements error
|
||||
func (he *HTTPError) Error() string {
|
||||
return fmt.Sprintf("http status code: %d while fetching url %s",
|
||||
he.StatusCode, he.URL)
|
||||
}
|
||||
|
||||
// ReadURL read contents from given url
|
||||
func ReadURL(url string, client *http.Client, header *http.Header) (body []byte, err error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if header != nil {
|
||||
req.Header = *header
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
klog.V(2).Infof("body of failing http response: %v", resp.Body)
|
||||
return nil, &HTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
limitedReader := &io.LimitedReader{R: resp.Body, N: maxReadLength}
|
||||
contents, err := ioutil.ReadAll(limitedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if limitedReader.N <= 0 {
|
||||
return nil, errors.New("the read limit is reached")
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
// 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 nil, err
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readDockerConfigJSONFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
|
||||
var cfgJSON DockerConfigJSON
|
||||
if err = json.Unmarshal(contents, &cfgJSON); err != nil {
|
||||
return nil, errors.New("error occurred while trying to unmarshal json")
|
||||
}
|
||||
cfg = cfgJSON.Auths
|
||||
return
|
||||
}
|
||||
|
||||
// dockerConfigEntryWithAuth is used solely for deserializing the Auth field
|
||||
// into a dockerConfigEntry during JSON deserialization.
|
||||
type dockerConfigEntryWithAuth struct {
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// +optional
|
||||
Password string `json:"password,omitempty"`
|
||||
// +optional
|
||||
Email string `json:"email,omitempty"`
|
||||
// +optional
|
||||
Auth string `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (ident *DockerConfigEntry) UnmarshalJSON(data []byte) error {
|
||||
var tmp dockerConfigEntryWithAuth
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ident.Username = tmp.Username
|
||||
ident.Password = tmp.Password
|
||||
ident.Email = tmp.Email
|
||||
|
||||
if len(tmp.Auth) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ident.Username, ident.Password, err = decodeDockerConfigFieldAuth(tmp.Auth)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (ident DockerConfigEntry) MarshalJSON() ([]byte, error) {
|
||||
toEncode := dockerConfigEntryWithAuth{ident.Username, ident.Password, ident.Email, ""}
|
||||
toEncode.Auth = encodeDockerConfigFieldAuth(ident.Username, ident.Password)
|
||||
|
||||
return json.Marshal(toEncode)
|
||||
}
|
||||
|
||||
// decodeDockerConfigFieldAuth deserializes the "auth" field from dockercfg into a
|
||||
// username and a password. The format of the auth field is base64(<username>:<password>).
|
||||
func decodeDockerConfigFieldAuth(field string) (username, password string, err error) {
|
||||
|
||||
var decoded []byte
|
||||
|
||||
// StdEncoding can only decode padded string
|
||||
// RawStdEncoding can only decode unpadded string
|
||||
if strings.HasSuffix(strings.TrimSpace(field), "=") {
|
||||
// decode padded data
|
||||
decoded, err = base64.StdEncoding.DecodeString(field)
|
||||
} else {
|
||||
// decode unpadded data
|
||||
decoded, err = base64.RawStdEncoding.DecodeString(field)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("unable to parse auth field, must be formatted as base64(username:password)")
|
||||
return
|
||||
}
|
||||
|
||||
username = parts[0]
|
||||
password = parts[1]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func encodeDockerConfigFieldAuth(username, password string) string {
|
||||
fieldValue := username + ":" + password
|
||||
|
||||
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
|
||||
}
|
@@ -1,404 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadDockerConfigFile(t *testing.T) {
|
||||
configJSONFileName := "config.json"
|
||||
var fileInfo *os.File
|
||||
|
||||
//test dockerconfig json
|
||||
inputDockerconfigJSONFile := "{ \"auths\": { \"http://foo.example.com\":{\"auth\":\"Zm9vOmJhcgo=\",\"email\":\"foo@example.com\"}}}"
|
||||
|
||||
preferredPath, err := ioutil.TempDir("", "test_foo_bar_dockerconfigjson_")
|
||||
if err != nil {
|
||||
t.Fatalf("Creating tmp dir fail: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(preferredPath)
|
||||
absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(preferredPath, configJSONFileName))
|
||||
if err != nil {
|
||||
t.Fatalf("While trying to canonicalize %s: %v", preferredPath, err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absDockerConfigFileLocation); os.IsNotExist(err) {
|
||||
//create test cfg file
|
||||
fileInfo, err = os.OpenFile(absDockerConfigFileLocation, os.O_CREATE|os.O_RDWR, 0664)
|
||||
if err != nil {
|
||||
t.Fatalf("While trying to create file %s: %v", absDockerConfigFileLocation, err)
|
||||
}
|
||||
defer fileInfo.Close()
|
||||
}
|
||||
|
||||
fileInfo.WriteString(inputDockerconfigJSONFile)
|
||||
|
||||
orgPreferredPath := GetPreferredDockercfgPath()
|
||||
SetPreferredDockercfgPath(preferredPath)
|
||||
defer SetPreferredDockercfgPath(orgPreferredPath)
|
||||
if _, err := ReadDockerConfigFile(); err != nil {
|
||||
t.Errorf("Getting docker config file fail : %v preferredPath : %q", err, preferredPath)
|
||||
}
|
||||
}
|
||||
func TestDockerConfigJsonJSONDecode(t *testing.T) {
|
||||
// Fake values for testing.
|
||||
input := []byte(`{"auths": {"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"}}}`)
|
||||
|
||||
expect := DockerConfigJSON{
|
||||
Auths: DockerConfig(map[string]DockerConfigEntry{
|
||||
"http://foo.example.com": {
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
"http://bar.example.com": {
|
||||
Username: "bar",
|
||||
Password: "baz",
|
||||
Email: "bar@example.com",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
var output DockerConfigJSON
|
||||
err := json.Unmarshal(input, &output)
|
||||
if err != nil {
|
||||
t.Errorf("Received unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expect, output) {
|
||||
t.Errorf("Received unexpected output. Expected %#v, got %#v", expect, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerConfigJSONDecode(t *testing.T) {
|
||||
// Fake values for testing.
|
||||
input := []byte(`{"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"}}`)
|
||||
|
||||
expect := DockerConfig(map[string]DockerConfigEntry{
|
||||
"http://foo.example.com": {
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
"http://bar.example.com": {
|
||||
Username: "bar",
|
||||
Password: "baz",
|
||||
Email: "bar@example.com",
|
||||
},
|
||||
})
|
||||
|
||||
var output DockerConfig
|
||||
err := json.Unmarshal(input, &output)
|
||||
if err != nil {
|
||||
t.Errorf("Received unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expect, output) {
|
||||
t.Errorf("Received unexpected output. Expected %#v, got %#v", expect, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerConfigEntryJSONDecode(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []byte
|
||||
expect DockerConfigEntry
|
||||
fail bool
|
||||
}{
|
||||
// simple case, just decode the fields
|
||||
{
|
||||
// Fake values for testing.
|
||||
input: []byte(`{"username": "foo", "password": "bar", "email": "foo@example.com"}`),
|
||||
expect: DockerConfigEntry{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
fail: false,
|
||||
},
|
||||
|
||||
// auth field decodes to username & password
|
||||
{
|
||||
input: []byte(`{"auth": "Zm9vOmJhcg==", "email": "foo@example.com"}`),
|
||||
expect: DockerConfigEntry{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
fail: false,
|
||||
},
|
||||
|
||||
// auth field overrides username & password
|
||||
{
|
||||
// Fake values for testing.
|
||||
input: []byte(`{"username": "foo", "password": "bar", "auth": "cGluZzpwb25n", "email": "foo@example.com"}`),
|
||||
expect: DockerConfigEntry{
|
||||
Username: "ping",
|
||||
Password: "pong",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
fail: false,
|
||||
},
|
||||
|
||||
// poorly-formatted auth causes failure
|
||||
{
|
||||
input: []byte(`{"auth": "pants", "email": "foo@example.com"}`),
|
||||
expect: DockerConfigEntry{
|
||||
Username: "",
|
||||
Password: "",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
fail: true,
|
||||
},
|
||||
|
||||
// invalid JSON causes failure
|
||||
{
|
||||
input: []byte(`{"email": false}`),
|
||||
expect: DockerConfigEntry{
|
||||
Username: "",
|
||||
Password: "",
|
||||
Email: "",
|
||||
},
|
||||
fail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
var output DockerConfigEntry
|
||||
err := json.Unmarshal(tt.input, &output)
|
||||
if (err != nil) != tt.fail {
|
||||
t.Errorf("case %d: expected fail=%t, got err=%v", i, tt.fail, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.expect, output) {
|
||||
t.Errorf("case %d: expected output %#v, got %#v", i, tt.expect, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeDockerConfigFieldAuth(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
username string
|
||||
password string
|
||||
fail bool
|
||||
}{
|
||||
// auth field decodes to username & password
|
||||
{
|
||||
input: "Zm9vOmJhcg==",
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
|
||||
// some test as before but with field not well padded
|
||||
{
|
||||
input: "Zm9vOmJhcg",
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
|
||||
// some test as before but with new line characters
|
||||
{
|
||||
input: "Zm9vOm\nJhcg==\n",
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
|
||||
// standard encoding (with padding)
|
||||
{
|
||||
input: base64.StdEncoding.EncodeToString([]byte("foo:bar")),
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
|
||||
// raw encoding (without padding)
|
||||
{
|
||||
input: base64.RawStdEncoding.EncodeToString([]byte("foo:bar")),
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
|
||||
// the input is encoded with encodeDockerConfigFieldAuth (standard encoding)
|
||||
{
|
||||
input: encodeDockerConfigFieldAuth("foo", "bar"),
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
|
||||
// good base64 data, but no colon separating username & password
|
||||
{
|
||||
input: "cGFudHM=",
|
||||
fail: true,
|
||||
},
|
||||
|
||||
// only new line characters are ignored
|
||||
{
|
||||
input: "Zm9vOmJhcg== ",
|
||||
fail: true,
|
||||
},
|
||||
|
||||
// bad base64 data
|
||||
{
|
||||
input: "pants",
|
||||
fail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
username, password, err := decodeDockerConfigFieldAuth(tt.input)
|
||||
if (err != nil) != tt.fail {
|
||||
t.Errorf("case %d: expected fail=%t, got err=%v", i, tt.fail, err)
|
||||
}
|
||||
|
||||
if tt.username != username {
|
||||
t.Errorf("case %d: expected username %q, got %q", i, tt.username, username)
|
||||
}
|
||||
|
||||
if tt.password != password {
|
||||
t.Errorf("case %d: expected password %q, got %q", i, tt.password, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerConfigEntryJSONCompatibleEncode(t *testing.T) {
|
||||
tests := []struct {
|
||||
input DockerConfigEntry
|
||||
expect []byte
|
||||
}{
|
||||
// simple case, just decode the fields
|
||||
{
|
||||
// Fake values for testing.
|
||||
expect: []byte(`{"username":"foo","password":"bar","email":"foo@example.com","auth":"Zm9vOmJhcg=="}`),
|
||||
input: DockerConfigEntry{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
actual, err := json.Marshal(tt.input)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||
}
|
||||
|
||||
if string(tt.expect) != string(actual) {
|
||||
t.Errorf("case %d: expected %v, got %v", i, string(tt.expect), string(actual))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDockerConfigFileFromBytes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
id string
|
||||
input []byte
|
||||
expectedCfg DockerConfig
|
||||
errorExpected bool
|
||||
expectedErrorMsg string
|
||||
}{
|
||||
{
|
||||
id: "valid input, no error expected",
|
||||
input: []byte(`{"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}}`),
|
||||
expectedCfg: DockerConfig(map[string]DockerConfigEntry{
|
||||
"http://foo.example.com": {
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "invalid input, error expected",
|
||||
input: []byte(`{"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"`),
|
||||
errorExpected: true,
|
||||
expectedErrorMsg: "error occurred while trying to unmarshal json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cfg, err := readDockerConfigFileFromBytes(tc.input)
|
||||
if err != nil && !tc.errorExpected {
|
||||
t.Fatalf("Error was not expected: %v", err)
|
||||
}
|
||||
if err != nil && tc.errorExpected {
|
||||
if !reflect.DeepEqual(err.Error(), tc.expectedErrorMsg) {
|
||||
t.Fatalf("Expected error message: `%s` got `%s`", tc.expectedErrorMsg, err.Error())
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(cfg, tc.expectedCfg) {
|
||||
t.Fatalf("expected: %v got %v", tc.expectedCfg, cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDockerConfigJSONFileFromBytes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
id string
|
||||
input []byte
|
||||
expectedCfg DockerConfig
|
||||
errorExpected bool
|
||||
expectedErrorMsg string
|
||||
}{
|
||||
{
|
||||
id: "valid input, no error expected",
|
||||
input: []byte(`{"auths": {"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"}}}`),
|
||||
expectedCfg: DockerConfig(map[string]DockerConfigEntry{
|
||||
"http://foo.example.com": {
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
"http://bar.example.com": {
|
||||
Username: "bar",
|
||||
Password: "baz",
|
||||
Email: "bar@example.com",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "invalid input, error expected",
|
||||
input: []byte(`{"auths": {"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"`),
|
||||
errorExpected: true,
|
||||
expectedErrorMsg: "error occurred while trying to unmarshal json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cfg, err := readDockerConfigJSONFileFromBytes(tc.input)
|
||||
if err != nil && !tc.errorExpected {
|
||||
t.Fatalf("Error was not expected: %v", err)
|
||||
}
|
||||
if err != nil && tc.errorExpected {
|
||||
if !reflect.DeepEqual(err.Error(), tc.expectedErrorMsg) {
|
||||
t.Fatalf("Expected error message: `%s` got `%s`", tc.expectedErrorMsg, err.Error())
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(cfg, tc.expectedCfg) {
|
||||
t.Fatalf("expected: %v got %v", tc.expectedCfg, cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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()
|
||||
|
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// DockerConfigProvider is the interface that registered extensions implement
|
||||
// to materialize 'dockercfg' credentials.
|
||||
type DockerConfigProvider interface {
|
||||
// Enabled returns true if the config provider is enabled.
|
||||
// Implementations can be blocking - e.g. metadata server unavailable.
|
||||
Enabled() bool
|
||||
// Provide returns docker configuration.
|
||||
// Implementations can be blocking - e.g. metadata server unavailable.
|
||||
// The image is passed in as context in the event that the
|
||||
// implementation depends on information in the image name to return
|
||||
// credentials; implementations are safe to ignore the image.
|
||||
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.
|
||||
type CachingDockerConfigProvider struct {
|
||||
Provider DockerConfigProvider
|
||||
Lifetime time.Duration
|
||||
|
||||
// ShouldCache is an optional function that returns true if the specific config should be cached.
|
||||
// If nil, all configs are treated as cacheable.
|
||||
ShouldCache func(DockerConfig) bool
|
||||
|
||||
// cache fields
|
||||
cacheDockerConfig DockerConfig
|
||||
expiration time.Time
|
||||
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()
|
||||
}
|
||||
|
||||
// Provide implements dockerConfigProvider
|
||||
func (d *CachingDockerConfigProvider) Provide(image string) DockerConfig {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
// If the cache hasn't expired, return our cache
|
||||
if time.Now().Before(d.expiration) {
|
||||
return d.cacheDockerConfig
|
||||
}
|
||||
|
||||
klog.V(2).Infof("Refreshing cache for provider: %v", reflect.TypeOf(d.Provider).String())
|
||||
config := d.Provider.Provide(image)
|
||||
if d.ShouldCache == nil || d.ShouldCache(config) {
|
||||
d.cacheDockerConfig = config
|
||||
d.expiration = time.Now().Add(d.Lifetime)
|
||||
}
|
||||
return config
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCachingProvider(t *testing.T) {
|
||||
provider := &testProvider{
|
||||
Count: 0,
|
||||
}
|
||||
|
||||
cache := &CachingDockerConfigProvider{
|
||||
Provider: provider,
|
||||
Lifetime: 1 * time.Second,
|
||||
}
|
||||
|
||||
image := "image"
|
||||
|
||||
if provider.Count != 0 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
if provider.Count != 1 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
|
||||
time.Sleep(cache.Lifetime)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
if provider.Count != 2 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
|
||||
time.Sleep(cache.Lifetime)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
cache.Provide(image)
|
||||
if provider.Count != 3 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user