github.com/enmand/kubernetes@v1.2.0-alpha.0/pkg/credentialprovider/gcp/metadata.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     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_credentials
    18  
    19  import (
    20  	"encoding/json"
    21  	"net/http"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/golang/glog"
    26  	"k8s.io/kubernetes/pkg/credentialprovider"
    27  )
    28  
    29  const (
    30  	metadataUrl              = "http://metadata.google.internal./computeMetadata/v1/"
    31  	metadataAttributes       = metadataUrl + "instance/attributes/"
    32  	dockerConfigKey          = metadataAttributes + "google-dockercfg"
    33  	dockerConfigUrlKey       = metadataAttributes + "google-dockercfg-url"
    34  	metadataScopes           = metadataUrl + "instance/service-accounts/default/scopes"
    35  	metadataToken            = metadataUrl + "instance/service-accounts/default/token"
    36  	metadataEmail            = metadataUrl + "instance/service-accounts/default/email"
    37  	storageScopePrefix       = "https://www.googleapis.com/auth/devstorage"
    38  	cloudPlatformScopePrefix = "https://www.googleapis.com/auth/cloud-platform"
    39  )
    40  
    41  // For these urls, the parts of the host name can be glob, for example '*.gcr.io" will match
    42  // "foo.gcr.io" and "bar.gcr.io".
    43  var containerRegistryUrls = []string{"container.cloud.google.com", "gcr.io", "*.gcr.io"}
    44  
    45  var metadataHeader = &http.Header{
    46  	"Metadata-Flavor": []string{"Google"},
    47  }
    48  
    49  // A DockerConfigProvider that reads its configuration from Google
    50  // Compute Engine metadata.
    51  type metadataProvider struct {
    52  	Client *http.Client
    53  }
    54  
    55  // A DockerConfigProvider that reads its configuration from a specific
    56  // Google Compute Engine metadata key: 'google-dockercfg'.
    57  type dockerConfigKeyProvider struct {
    58  	metadataProvider
    59  }
    60  
    61  // A DockerConfigProvider that reads its configuration from a URL read from
    62  // a specific Google Compute Engine metadata key: 'google-dockercfg-url'.
    63  type dockerConfigUrlKeyProvider struct {
    64  	metadataProvider
    65  }
    66  
    67  // A DockerConfigProvider that provides a dockercfg with:
    68  //    Username: "_token"
    69  //    Password: "{access token from metadata}"
    70  type containerRegistryProvider struct {
    71  	metadataProvider
    72  }
    73  
    74  // init registers the various means by which credentials may
    75  // be resolved on GCP.
    76  func init() {
    77  	credentialprovider.RegisterCredentialProvider("google-dockercfg",
    78  		&credentialprovider.CachingDockerConfigProvider{
    79  			Provider: &dockerConfigKeyProvider{
    80  				metadataProvider{Client: http.DefaultClient},
    81  			},
    82  			Lifetime: 60 * time.Second,
    83  		})
    84  
    85  	credentialprovider.RegisterCredentialProvider("google-dockercfg-url",
    86  		&credentialprovider.CachingDockerConfigProvider{
    87  			Provider: &dockerConfigUrlKeyProvider{
    88  				metadataProvider{Client: http.DefaultClient},
    89  			},
    90  			Lifetime: 60 * time.Second,
    91  		})
    92  
    93  	credentialprovider.RegisterCredentialProvider("google-container-registry",
    94  		// Never cache this.  The access token is already
    95  		// cached by the metadata service.
    96  		&containerRegistryProvider{
    97  			metadataProvider{Client: http.DefaultClient},
    98  		})
    99  }
   100  
   101  // Enabled implements DockerConfigProvider for all of the Google implementations.
   102  func (g *metadataProvider) Enabled() bool {
   103  	_, err := credentialprovider.ReadUrl(metadataUrl, g.Client, metadataHeader)
   104  	return err == nil
   105  }
   106  
   107  // Provide implements DockerConfigProvider
   108  func (g *dockerConfigKeyProvider) Provide() credentialprovider.DockerConfig {
   109  	// Read the contents of the google-dockercfg metadata key and
   110  	// parse them as an alternate .dockercfg
   111  	if cfg, err := credentialprovider.ReadDockerConfigFileFromUrl(dockerConfigKey, g.Client, metadataHeader); err != nil {
   112  		glog.Errorf("while reading 'google-dockercfg' metadata: %v", err)
   113  	} else {
   114  		return cfg
   115  	}
   116  
   117  	return credentialprovider.DockerConfig{}
   118  }
   119  
   120  // Provide implements DockerConfigProvider
   121  func (g *dockerConfigUrlKeyProvider) Provide() credentialprovider.DockerConfig {
   122  	// Read the contents of the google-dockercfg-url key and load a .dockercfg from there
   123  	if url, err := credentialprovider.ReadUrl(dockerConfigUrlKey, g.Client, metadataHeader); err != nil {
   124  		glog.Errorf("while reading 'google-dockercfg-url' metadata: %v", err)
   125  	} else {
   126  		if strings.HasPrefix(string(url), "http") {
   127  			if cfg, err := credentialprovider.ReadDockerConfigFileFromUrl(string(url), g.Client, nil); err != nil {
   128  				glog.Errorf("while reading 'google-dockercfg-url'-specified url: %s, %v", string(url), err)
   129  			} else {
   130  				return cfg
   131  			}
   132  		} else {
   133  			// TODO(mattmoor): support reading alternate scheme URLs (e.g. gs:// or s3://)
   134  			glog.Errorf("Unsupported URL scheme: %s", string(url))
   135  		}
   136  	}
   137  
   138  	return credentialprovider.DockerConfig{}
   139  }
   140  
   141  // Enabled implements a special metadata-based check, which verifies the
   142  // storage scope is available on the GCE VM.
   143  func (g *containerRegistryProvider) Enabled() bool {
   144  	value, err := credentialprovider.ReadUrl(metadataScopes+"?alt=json", g.Client, metadataHeader)
   145  	if err != nil {
   146  		return false
   147  	}
   148  	var scopes []string
   149  	if err := json.Unmarshal([]byte(value), &scopes); err != nil {
   150  		return false
   151  	}
   152  
   153  	for _, v := range scopes {
   154  		// cloudPlatformScope implies storage scope.
   155  		if strings.HasPrefix(v, storageScopePrefix) || strings.HasPrefix(v, cloudPlatformScopePrefix) {
   156  			return true
   157  		}
   158  	}
   159  	glog.Warningf("Google container registry is disabled, no storage scope is available: %s", value)
   160  	return false
   161  }
   162  
   163  // tokenBlob is used to decode the JSON blob containing an access token
   164  // that is returned by GCE metadata.
   165  type tokenBlob struct {
   166  	AccessToken string `json:"access_token"`
   167  }
   168  
   169  // Provide implements DockerConfigProvider
   170  func (g *containerRegistryProvider) Provide() credentialprovider.DockerConfig {
   171  	cfg := credentialprovider.DockerConfig{}
   172  
   173  	tokenJsonBlob, err := credentialprovider.ReadUrl(metadataToken, g.Client, metadataHeader)
   174  	if err != nil {
   175  		glog.Errorf("while reading access token endpoint: %v", err)
   176  		return cfg
   177  	}
   178  
   179  	email, err := credentialprovider.ReadUrl(metadataEmail, g.Client, metadataHeader)
   180  	if err != nil {
   181  		glog.Errorf("while reading email endpoint: %v", err)
   182  		return cfg
   183  	}
   184  
   185  	var parsedBlob tokenBlob
   186  	if err := json.Unmarshal([]byte(tokenJsonBlob), &parsedBlob); err != nil {
   187  		glog.Errorf("while parsing json blob %s: %v", tokenJsonBlob, err)
   188  		return cfg
   189  	}
   190  
   191  	entry := credentialprovider.DockerConfigEntry{
   192  		Username: "_token",
   193  		Password: parsedBlob.AccessToken,
   194  		Email:    string(email),
   195  	}
   196  
   197  	// Add our entry for each of the supported container registry URLs
   198  	for _, k := range containerRegistryUrls {
   199  		cfg[k] = entry
   200  	}
   201  	return cfg
   202  }