...

Source file src/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata.go

Documentation: k8s.io/kubernetes/pkg/credentialprovider/gcp

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package gcp
    18  
    19  import (
    20  	"encoding/json"
    21  	"net/http"
    22  	"os"
    23  	"os/exec"
    24  	"runtime"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	utilnet "k8s.io/apimachinery/pkg/util/net"
    30  	"k8s.io/cloud-provider/credentialconfig"
    31  	"k8s.io/klog/v2"
    32  	"k8s.io/kubernetes/pkg/credentialprovider"
    33  	"k8s.io/legacy-cloud-providers/gce/gcpcredential"
    34  )
    35  
    36  const (
    37  	metadataURL        = "http://metadata.google.internal./computeMetadata/v1/"
    38  	metadataAttributes = metadataURL + "instance/attributes/"
    39  	// DockerConfigKey is the URL of the dockercfg metadata key used by DockerConfigKeyProvider.
    40  	DockerConfigKey = metadataAttributes + "google-dockercfg"
    41  	// DockerConfigURLKey is the URL of the dockercfg metadata key used by DockerConfigURLKeyProvider.
    42  	DockerConfigURLKey = metadataAttributes + "google-dockercfg-url"
    43  	serviceAccounts    = metadataURL + "instance/service-accounts/"
    44  	metadataScopes     = metadataURL + "instance/service-accounts/default/scopes"
    45  	// StorageScopePrefix is the prefix checked by ContainerRegistryProvider.Enabled.
    46  	StorageScopePrefix       = "https://www.googleapis.com/auth/devstorage"
    47  	cloudPlatformScopePrefix = "https://www.googleapis.com/auth/cloud-platform"
    48  	defaultServiceAccount    = "default/"
    49  )
    50  
    51  // gceProductNameFile is the product file path that contains the cloud service name.
    52  // This is a variable instead of a const to enable testing.
    53  var gceProductNameFile = "/sys/class/dmi/id/product_name"
    54  
    55  var metadataHeader = &http.Header{
    56  	"Metadata-Flavor": []string{"Google"},
    57  }
    58  
    59  var warnOnce sync.Once
    60  
    61  // init registers the various means by which credentials may
    62  // be resolved on GCP.
    63  func init() {
    64  	tr := utilnet.SetTransportDefaults(&http.Transport{})
    65  	metadataHTTPClientTimeout := time.Second * 10
    66  	httpClient := &http.Client{
    67  		Transport: tr,
    68  		Timeout:   metadataHTTPClientTimeout,
    69  	}
    70  	credentialprovider.RegisterCredentialProvider("google-dockercfg",
    71  		&credentialprovider.CachingDockerConfigProvider{
    72  			Provider: &DockerConfigKeyProvider{
    73  				MetadataProvider: MetadataProvider{Client: httpClient},
    74  			},
    75  			Lifetime: 60 * time.Second,
    76  		})
    77  
    78  	credentialprovider.RegisterCredentialProvider("google-dockercfg-url",
    79  		&credentialprovider.CachingDockerConfigProvider{
    80  			Provider: &DockerConfigURLKeyProvider{
    81  				MetadataProvider: MetadataProvider{Client: httpClient},
    82  			},
    83  			Lifetime: 60 * time.Second,
    84  		})
    85  
    86  	credentialprovider.RegisterCredentialProvider("google-container-registry",
    87  		// Never cache this.  The access token is already
    88  		// cached by the metadata service.
    89  		&ContainerRegistryProvider{
    90  			MetadataProvider: MetadataProvider{Client: httpClient},
    91  		})
    92  }
    93  
    94  // MetadataProvider is a DockerConfigProvider that reads its configuration from Google
    95  // Compute Engine metadata.
    96  type MetadataProvider struct {
    97  	Client *http.Client
    98  }
    99  
   100  // DockerConfigKeyProvider is a DockerConfigProvider that reads its configuration from a specific
   101  // Google Compute Engine metadata key: 'google-dockercfg'.
   102  type DockerConfigKeyProvider struct {
   103  	MetadataProvider
   104  }
   105  
   106  // DockerConfigURLKeyProvider is a DockerConfigProvider that reads its configuration from a URL read from
   107  // a specific Google Compute Engine metadata key: 'google-dockercfg-url'.
   108  type DockerConfigURLKeyProvider struct {
   109  	MetadataProvider
   110  }
   111  
   112  // ContainerRegistryProvider is a DockerConfigProvider that provides a dockercfg with:
   113  //
   114  //	Username: "_token"
   115  //	Password: "{access token from metadata}"
   116  type ContainerRegistryProvider struct {
   117  	MetadataProvider
   118  }
   119  
   120  // Returns true if it finds a local GCE VM.
   121  // Looks at a product file that is an undocumented API.
   122  func onGCEVM() bool {
   123  	var name string
   124  
   125  	if runtime.GOOS == "windows" {
   126  		data, err := exec.Command("wmic", "computersystem", "get", "model").Output()
   127  		if err != nil {
   128  			return false
   129  		}
   130  		fields := strings.Split(strings.TrimSpace(string(data)), "\r\n")
   131  		if len(fields) != 2 {
   132  			klog.V(2).Infof("Received unexpected value retrieving system model: %q", string(data))
   133  			return false
   134  		}
   135  		name = fields[1]
   136  	} else {
   137  		data, err := os.ReadFile(gceProductNameFile)
   138  		if err != nil {
   139  			klog.V(2).Infof("Error while reading product_name: %v", err)
   140  			return false
   141  		}
   142  		name = strings.TrimSpace(string(data))
   143  	}
   144  	return name == "Google" || name == "Google Compute Engine"
   145  }
   146  
   147  // Enabled implements DockerConfigProvider for all of the Google implementations.
   148  func (g *MetadataProvider) Enabled() bool {
   149  	onGCE := onGCEVM()
   150  	if !onGCE {
   151  		return false
   152  	}
   153  	if credentialprovider.AreLegacyCloudCredentialProvidersDisabled() {
   154  		warnOnce.Do(func() {
   155  			klog.V(4).Infof("GCP credential provider is now disabled. Please refer to sig-cloud-provider for guidance on external credential provider integration for GCP")
   156  		})
   157  		return false
   158  	}
   159  	return true
   160  }
   161  
   162  // Provide implements DockerConfigProvider
   163  func (g *DockerConfigKeyProvider) Provide(image string) credentialprovider.DockerConfig {
   164  	return registryToDocker(gcpcredential.ProvideConfigKey(g.Client, image))
   165  }
   166  
   167  // Provide implements DockerConfigProvider
   168  func (g *DockerConfigURLKeyProvider) Provide(image string) credentialprovider.DockerConfig {
   169  	return registryToDocker(gcpcredential.ProvideURLKey(g.Client, image))
   170  }
   171  
   172  // runWithBackoff runs input function `f` with an exponential backoff.
   173  // Note that this method can block indefinitely.
   174  func runWithBackoff(f func() ([]byte, error)) []byte {
   175  	var backoff = 100 * time.Millisecond
   176  	const maxBackoff = time.Minute
   177  	for {
   178  		value, err := f()
   179  		if err == nil {
   180  			return value
   181  		}
   182  		time.Sleep(backoff)
   183  		backoff = backoff * 2
   184  		if backoff > maxBackoff {
   185  			backoff = maxBackoff
   186  		}
   187  	}
   188  }
   189  
   190  // Enabled implements a special metadata-based check, which verifies the
   191  // storage scope is available on the GCE VM.
   192  // If running on a GCE VM, check if 'default' service account exists.
   193  // If it does not exist, assume that registry is not enabled.
   194  // If default service account exists, check if relevant scopes exist in the default service account.
   195  // The metadata service can become temporarily inaccesible. Hence all requests to the metadata
   196  // service will be retried until the metadata server returns a `200`.
   197  // It is expected that "http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/" will return a `200`
   198  // and "http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/default/scopes" will also return `200`.
   199  // More information on metadata service can be found here - https://cloud.google.com/compute/docs/storing-retrieving-metadata
   200  func (g *ContainerRegistryProvider) Enabled() bool {
   201  	if !onGCEVM() {
   202  		return false
   203  	}
   204  
   205  	if credentialprovider.AreLegacyCloudCredentialProvidersDisabled() {
   206  		warnOnce.Do(func() {
   207  			klog.V(4).Infof("GCP credential provider is now disabled. Please refer to sig-cloud-provider for guidance on external credential provider integration for GCP")
   208  		})
   209  		return false
   210  	}
   211  
   212  	// Given that we are on GCE, we should keep retrying until the metadata server responds.
   213  	value := runWithBackoff(func() ([]byte, error) {
   214  		value, err := gcpcredential.ReadURL(serviceAccounts, g.Client, metadataHeader)
   215  		if err != nil {
   216  			klog.V(2).Infof("Failed to Get service accounts from gce metadata server: %v", err)
   217  		}
   218  		return value, err
   219  	})
   220  	// We expect the service account to return a list of account directories separated by newlines, e.g.,
   221  	//   sv-account-name1/
   222  	//   sv-account-name2/
   223  	// ref: https://cloud.google.com/compute/docs/storing-retrieving-metadata
   224  	defaultServiceAccountExists := false
   225  	for _, sa := range strings.Split(string(value), "\n") {
   226  		if strings.TrimSpace(sa) == defaultServiceAccount {
   227  			defaultServiceAccountExists = true
   228  			break
   229  		}
   230  	}
   231  	if !defaultServiceAccountExists {
   232  		klog.V(2).Infof("'default' service account does not exist. Found following service accounts: %q", string(value))
   233  		return false
   234  	}
   235  	url := metadataScopes + "?alt=json"
   236  	value = runWithBackoff(func() ([]byte, error) {
   237  		value, err := gcpcredential.ReadURL(url, g.Client, metadataHeader)
   238  		if err != nil {
   239  			klog.V(2).Infof("Failed to Get scopes in default service account from gce metadata server: %v", err)
   240  		}
   241  		return value, err
   242  	})
   243  	var scopes []string
   244  	if err := json.Unmarshal(value, &scopes); err != nil {
   245  		klog.Errorf("Failed to unmarshal scopes: %v", err)
   246  		return false
   247  	}
   248  	for _, v := range scopes {
   249  		// cloudPlatformScope implies storage scope.
   250  		if strings.HasPrefix(v, StorageScopePrefix) || strings.HasPrefix(v, cloudPlatformScopePrefix) {
   251  			return true
   252  		}
   253  	}
   254  	klog.Warningf("Google container registry is disabled, no storage scope is available: %s", value)
   255  	return false
   256  }
   257  
   258  // Provide implements DockerConfigProvider
   259  func (g *ContainerRegistryProvider) Provide(image string) credentialprovider.DockerConfig {
   260  	return registryToDocker(gcpcredential.ProvideContainerRegistry(g.Client, image))
   261  }
   262  
   263  func registryToDocker(registryConfig credentialconfig.RegistryConfig) credentialprovider.DockerConfig {
   264  	dockerConfig := credentialprovider.DockerConfig{}
   265  	for k, v := range registryConfig {
   266  		dockerConfig[k] = credentialprovider.DockerConfigEntry{
   267  			Username: v.Username,
   268  			Password: v.Password,
   269  			Email:    v.Email,
   270  		}
   271  	}
   272  	return dockerConfig
   273  }
   274  

View as plain text