mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 13:07:07 +00:00
The test in pkg/credentialprovider/gcp/metadata_test.go is meant to run only on GCE, which means that it will fail in any other case. We should skip the test if we're not testing in GCE or Windows in GCE.
450 lines
14 KiB
Go
450 lines
14 KiB
Go
/*
|
|
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 gcp
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
|
"k8s.io/kubernetes/pkg/credentialprovider"
|
|
"k8s.io/legacy-cloud-providers/gce/gcpcredential"
|
|
)
|
|
|
|
func createProductNameFile() (string, error) {
|
|
file, err := ioutil.TempFile("", "")
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create temporary test file: %v", err)
|
|
}
|
|
return file.Name(), ioutil.WriteFile(file.Name(), []byte("Google"), 0600)
|
|
}
|
|
|
|
// The tests here are run in this fashion to ensure TestAllProvidersNoMetadata
|
|
// is run after the others, since that test currently relies upon the file
|
|
// referenced by gceProductNameFile being removed, which is the opposite of
|
|
// the other tests
|
|
func TestMetadata(t *testing.T) {
|
|
// This test requires onGCEVM to return True. On Linux, this can be faked by creating a
|
|
// Product Name File. But on Windows, onGCEVM makes the following syscall instead:
|
|
// wmic computersystem get model
|
|
if runtime.GOOS == "windows" && !onGCEVM() {
|
|
t.Skip("Skipping test on Windows, not on GCE.")
|
|
}
|
|
var err error
|
|
gceProductNameFile, err = createProductNameFile()
|
|
if err != nil {
|
|
t.Errorf("failed to create gce product name file: %v", err)
|
|
}
|
|
defer os.Remove(gceProductNameFile)
|
|
t.Run("productNameDependent", func(t *testing.T) {
|
|
t.Run("DockerKeyringFromGoogleDockerConfigMetadata",
|
|
DockerKeyringFromGoogleDockerConfigMetadata)
|
|
t.Run("DockerKeyringFromGoogleDockerConfigMetadataUrl",
|
|
DockerKeyringFromGoogleDockerConfigMetadataURL)
|
|
t.Run("ContainerRegistryNoServiceAccount",
|
|
ContainerRegistryNoServiceAccount)
|
|
t.Run("ContainerRegistryBasics",
|
|
ContainerRegistryBasics)
|
|
t.Run("ContainerRegistryNoStorageScope",
|
|
ContainerRegistryNoStorageScope)
|
|
t.Run("ComputePlatformScopeSubstitutesStorageScope",
|
|
ComputePlatformScopeSubstitutesStorageScope)
|
|
})
|
|
// We defer os.Remove in case of an unexpected exit, but this os.Remove call
|
|
// is the normal teardown call so AllProvidersNoMetadata executes properly
|
|
os.Remove(gceProductNameFile)
|
|
t.Run("AllProvidersNoMetadata",
|
|
AllProvidersNoMetadata)
|
|
}
|
|
|
|
func DockerKeyringFromGoogleDockerConfigMetadata(t *testing.T) {
|
|
t.Parallel()
|
|
registryURL := "hello.kubernetes.io"
|
|
email := "foo@bar.baz"
|
|
username := "foo"
|
|
password := "bar" // Fake value for testing.
|
|
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
|
sampleDockerConfig := fmt.Sprintf(`{
|
|
"https://%s": {
|
|
"email": %q,
|
|
"auth": %q
|
|
}
|
|
}`, registryURL, email, auth)
|
|
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(gcpcredential.DockerConfigKey, r.URL.Path) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintln(w, sampleDockerConfig)
|
|
} else {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
keyring := &credentialprovider.BasicDockerKeyring{}
|
|
provider := &DockerConfigKeyProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
}
|
|
|
|
if !provider.Enabled() {
|
|
t.Errorf("Provider is unexpectedly disabled")
|
|
}
|
|
|
|
keyring.Add(provider.Provide(""))
|
|
|
|
creds, ok := keyring.Lookup(registryURL)
|
|
if !ok {
|
|
t.Errorf("Didn't find expected URL: %s", registryURL)
|
|
return
|
|
}
|
|
if len(creds) > 1 {
|
|
t.Errorf("Got more hits than expected: %s", creds)
|
|
}
|
|
val := creds[0]
|
|
|
|
if username != val.Username {
|
|
t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username)
|
|
}
|
|
if password != val.Password {
|
|
t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password)
|
|
}
|
|
if email != val.Email {
|
|
t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
|
|
}
|
|
}
|
|
|
|
func DockerKeyringFromGoogleDockerConfigMetadataURL(t *testing.T) {
|
|
t.Parallel()
|
|
registryURL := "hello.kubernetes.io"
|
|
email := "foo@bar.baz"
|
|
username := "foo"
|
|
password := "bar" // Fake value for testing.
|
|
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
|
sampleDockerConfig := fmt.Sprintf(`{
|
|
"https://%s": {
|
|
"email": %q,
|
|
"auth": %q
|
|
}
|
|
}`, registryURL, email, auth)
|
|
const probeEndpoint = "/computeMetadata/v1/"
|
|
const valueEndpoint = "/my/value"
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Only serve the URL key and the value endpoint
|
|
if probeEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
} else if valueEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintln(w, sampleDockerConfig)
|
|
} 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)
|
|
} else {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
keyring := &credentialprovider.BasicDockerKeyring{}
|
|
provider := &DockerConfigURLKeyProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
}
|
|
|
|
if !provider.Enabled() {
|
|
t.Errorf("Provider is unexpectedly disabled")
|
|
}
|
|
|
|
keyring.Add(provider.Provide(""))
|
|
|
|
creds, ok := keyring.Lookup(registryURL)
|
|
if !ok {
|
|
t.Errorf("Didn't find expected URL: %s", registryURL)
|
|
return
|
|
}
|
|
if len(creds) > 1 {
|
|
t.Errorf("Got more hits than expected: %s", creds)
|
|
}
|
|
val := creds[0]
|
|
|
|
if username != val.Username {
|
|
t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username)
|
|
}
|
|
if password != val.Password {
|
|
t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password)
|
|
}
|
|
if email != val.Email {
|
|
t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
|
|
}
|
|
}
|
|
|
|
func ContainerRegistryBasics(t *testing.T) {
|
|
t.Parallel()
|
|
registryURLs := []string{"container.cloud.google.com", "eu.gcr.io", "us-west2-docker.pkg.dev"}
|
|
for _, registryURL := range registryURLs {
|
|
t.Run(registryURL, func(t *testing.T) {
|
|
email := "1234@project.gserviceaccount.com"
|
|
token := &gcpcredential.TokenBlob{AccessToken: "ya26.lots-of-indiscernible-garbage"} // Fake value for testing.
|
|
|
|
const (
|
|
serviceAccountsEndpoint = "/computeMetadata/v1/instance/service-accounts/"
|
|
defaultEndpoint = "/computeMetadata/v1/instance/service-accounts/default/"
|
|
scopeEndpoint = defaultEndpoint + "scopes"
|
|
emailEndpoint = defaultEndpoint + "email"
|
|
tokenEndpoint = defaultEndpoint + "token"
|
|
)
|
|
|
|
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"]`, gcpcredential.StorageScopePrefix)
|
|
} else if emailEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprint(w, email)
|
|
} else if tokenEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
bytes, err := json.Marshal(token)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
fmt.Fprintln(w, string(bytes))
|
|
} else if serviceAccountsEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintln(w, "default/\ncustom")
|
|
} else {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
keyring := &credentialprovider.BasicDockerKeyring{}
|
|
provider := &ContainerRegistryProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
}
|
|
|
|
if !provider.Enabled() {
|
|
t.Errorf("Provider is unexpectedly disabled")
|
|
}
|
|
|
|
keyring.Add(provider.Provide(""))
|
|
|
|
creds, ok := keyring.Lookup(registryURL)
|
|
if !ok {
|
|
t.Errorf("Didn't find expected URL: %s", registryURL)
|
|
return
|
|
}
|
|
if len(creds) > 1 {
|
|
t.Errorf("Got more hits than expected: %s", creds)
|
|
}
|
|
val := creds[0]
|
|
|
|
if val.Username != "_token" {
|
|
t.Errorf("Unexpected username value, want: %s, got: %s", "_token", val.Username)
|
|
}
|
|
if token.AccessToken != val.Password {
|
|
t.Errorf("Unexpected password value, want: %s, got: %s", token.AccessToken, val.Password)
|
|
}
|
|
if email != val.Email {
|
|
t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func ContainerRegistryNoServiceAccount(t *testing.T) {
|
|
const (
|
|
serviceAccountsEndpoint = "/computeMetadata/v1/instance/service-accounts/"
|
|
)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Only serve the URL key and the value endpoint
|
|
if serviceAccountsEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
bytes, err := json.Marshal([]string{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
fmt.Fprintln(w, string(bytes))
|
|
} else {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
provider := &ContainerRegistryProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
}
|
|
|
|
if provider.Enabled() {
|
|
t.Errorf("Provider is unexpectedly enabled")
|
|
}
|
|
}
|
|
|
|
func ContainerRegistryNoStorageScope(t *testing.T) {
|
|
t.Parallel()
|
|
const (
|
|
serviceAccountsEndpoint = "/computeMetadata/v1/instance/service-accounts/"
|
|
defaultEndpoint = "/computeMetadata/v1/instance/service-accounts/default/"
|
|
scopeEndpoint = defaultEndpoint + "scopes"
|
|
)
|
|
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.Fprint(w, `["https://www.googleapis.com/auth/compute.read_write"]`)
|
|
} else if serviceAccountsEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintln(w, "default/\ncustom")
|
|
} else {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
provider := &ContainerRegistryProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
}
|
|
|
|
if provider.Enabled() {
|
|
t.Errorf("Provider is unexpectedly enabled")
|
|
}
|
|
}
|
|
|
|
func ComputePlatformScopeSubstitutesStorageScope(t *testing.T) {
|
|
t.Parallel()
|
|
const (
|
|
serviceAccountsEndpoint = "/computeMetadata/v1/instance/service-accounts/"
|
|
defaultEndpoint = "/computeMetadata/v1/instance/service-accounts/default/"
|
|
scopeEndpoint = defaultEndpoint + "scopes"
|
|
)
|
|
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.Fprint(w, `["https://www.googleapis.com/auth/compute.read_write","https://www.googleapis.com/auth/cloud-platform.read-only"]`)
|
|
} else if serviceAccountsEndpoint == r.URL.Path {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintln(w, "default/\ncustom")
|
|
} else {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
provider := &ContainerRegistryProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
}
|
|
|
|
if !provider.Enabled() {
|
|
t.Errorf("Provider is unexpectedly disabled")
|
|
}
|
|
}
|
|
|
|
func AllProvidersNoMetadata(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "", http.StatusNotFound)
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Make a transport that reroutes all traffic to the example server
|
|
transport := utilnet.SetTransportDefaults(&http.Transport{
|
|
Proxy: func(req *http.Request) (*url.URL, error) {
|
|
return url.Parse(server.URL + req.URL.Path)
|
|
},
|
|
})
|
|
|
|
providers := []credentialprovider.DockerConfigProvider{
|
|
&DockerConfigKeyProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
},
|
|
&DockerConfigURLKeyProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
},
|
|
&ContainerRegistryProvider{
|
|
MetadataProvider: MetadataProvider{Client: &http.Client{Transport: transport}},
|
|
},
|
|
}
|
|
|
|
for _, provider := range providers {
|
|
if provider.Enabled() {
|
|
t.Errorf("Provider %s is unexpectedly enabled", reflect.TypeOf(provider).String())
|
|
}
|
|
}
|
|
}
|