kubernetes/pkg/credentialprovider/gcp/metadata_test.go
Claudiu Belu 1129f57143 unittests: Skip test if not on GCE
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.
2022-11-28 13:54:14 +00:00

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())
}
}
}