mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
vendor files
This commit is contained in:
56
vendor/k8s.io/kubernetes/pkg/credentialprovider/BUILD
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/pkg/credentialprovider/BUILD
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"doc.go",
|
||||
"keyring.go",
|
||||
"plugins.go",
|
||||
"provider.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider",
|
||||
deps = [
|
||||
"//vendor/github.com/docker/docker/api/types:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"config_test.go",
|
||||
"keyring_test.go",
|
||||
"provider_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider",
|
||||
library = ":go_default_library",
|
||||
deps = ["//vendor/github.com/docker/docker/api/types:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/credentialprovider/aws:all-srcs",
|
||||
"//pkg/credentialprovider/azure:all-srcs",
|
||||
"//pkg/credentialprovider/gcp:all-srcs",
|
||||
"//pkg/credentialprovider/rancher:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
25
vendor/k8s.io/kubernetes/pkg/credentialprovider/OWNERS
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/pkg/credentialprovider/OWNERS
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
approvers:
|
||||
- deads2k
|
||||
- erictune
|
||||
- liggitt
|
||||
reviewers:
|
||||
- thockin
|
||||
- smarterclayton
|
||||
- yujuhong
|
||||
- brendandburns
|
||||
- derekwaynecarr
|
||||
- vishh
|
||||
- mikedanese
|
||||
- davidopp
|
||||
- pmorie
|
||||
- dchen1107
|
||||
- justinsb
|
||||
- eparis
|
||||
- dims
|
||||
- resouer
|
||||
- mbohlool
|
||||
- david-mcmahon
|
||||
- mfojtik
|
||||
- therc
|
||||
- lixiaobing10051267
|
||||
- goltermann
|
46
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/BUILD
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/BUILD
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["aws_credentials.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/aws",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/request:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/service/ecr:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["aws_credentials_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/aws",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
|
||||
"//vendor/github.com/aws/aws-sdk-go/service/ecr:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
6
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/OWNERS
generated
vendored
Executable file
6
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/OWNERS
generated
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
reviewers:
|
||||
- justinsb
|
||||
- david-mcmahon
|
||||
- therc
|
||||
- lixiaobing10051267
|
||||
- goltermann
|
226
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials.go
generated
vendored
Normal file
226
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
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 credentials
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
const awsChinaRegionPrefix = "cn-"
|
||||
const awsStandardDNSSuffix = "amazonaws.com"
|
||||
const awsChinaDNSSuffix = "amazonaws.com.cn"
|
||||
const registryURLTemplate = "*.dkr.ecr.%s.%s"
|
||||
|
||||
// awsHandlerLogger is a handler that logs all AWS SDK requests
|
||||
// Copied from pkg/cloudprovider/providers/aws/log_handler.go
|
||||
func awsHandlerLogger(req *request.Request) {
|
||||
service := req.ClientInfo.ServiceName
|
||||
region := req.Config.Region
|
||||
|
||||
name := "?"
|
||||
if req.Operation != nil {
|
||||
name = req.Operation.Name
|
||||
}
|
||||
|
||||
glog.V(3).Infof("AWS request: %s:%s in %s", service, name, *region)
|
||||
}
|
||||
|
||||
// An interface for testing purposes.
|
||||
type tokenGetter interface {
|
||||
GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error)
|
||||
}
|
||||
|
||||
// The canonical implementation
|
||||
type ecrTokenGetter struct {
|
||||
svc *ecr.ECR
|
||||
}
|
||||
|
||||
func (p *ecrTokenGetter) GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error) {
|
||||
return p.svc.GetAuthorizationToken(input)
|
||||
}
|
||||
|
||||
// lazyEcrProvider is a DockerConfigProvider that creates on demand an
|
||||
// ecrProvider for a given region and then proxies requests to it.
|
||||
type lazyEcrProvider struct {
|
||||
region string
|
||||
regionURL string
|
||||
actualProvider *credentialprovider.CachingDockerConfigProvider
|
||||
}
|
||||
|
||||
var _ credentialprovider.DockerConfigProvider = &lazyEcrProvider{}
|
||||
|
||||
// ecrProvider is a DockerConfigProvider that gets and refreshes 12-hour tokens
|
||||
// from AWS to access ECR.
|
||||
type ecrProvider struct {
|
||||
region string
|
||||
regionURL string
|
||||
getter tokenGetter
|
||||
}
|
||||
|
||||
var _ credentialprovider.DockerConfigProvider = &ecrProvider{}
|
||||
|
||||
// registryURL has different suffix in AWS China region
|
||||
func registryURL(region string) string {
|
||||
dnsSuffix := awsStandardDNSSuffix
|
||||
// deal with aws none standard regions
|
||||
if strings.HasPrefix(region, awsChinaRegionPrefix) {
|
||||
dnsSuffix = awsChinaDNSSuffix
|
||||
}
|
||||
return fmt.Sprintf(registryURLTemplate, region, dnsSuffix)
|
||||
}
|
||||
|
||||
// RegisterCredentialsProvider registers a credential provider for the specified region.
|
||||
// It creates a lazy provider for each AWS region, in order to support
|
||||
// cross-region ECR access. They have to be lazy because it's unlikely, but not
|
||||
// impossible, that we'll use more than one.
|
||||
// This should be called only if using the AWS cloud provider.
|
||||
// This way, we avoid timeouts waiting for a non-existent provider.
|
||||
func RegisterCredentialsProvider(region string) {
|
||||
glog.V(4).Infof("registering credentials provider for AWS region %q", region)
|
||||
|
||||
credentialprovider.RegisterCredentialProvider("aws-ecr-"+region,
|
||||
&lazyEcrProvider{
|
||||
region: region,
|
||||
regionURL: registryURL(region),
|
||||
})
|
||||
}
|
||||
|
||||
// Enabled implements DockerConfigProvider.Enabled for the lazy provider.
|
||||
// Since we perform no checks/work of our own and actualProvider is only created
|
||||
// later at image pulling time (if ever), always return true.
|
||||
func (p *lazyEcrProvider) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider.LazyProvide. It will be called
|
||||
// by the client when attempting to pull an image and it will create the actual
|
||||
// provider only when we actually need it the first time.
|
||||
func (p *lazyEcrProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
if p.actualProvider == nil {
|
||||
glog.V(2).Infof("Creating ecrProvider for %s", p.region)
|
||||
p.actualProvider = &credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: newEcrProvider(p.region, nil),
|
||||
// Refresh credentials a little earlier than expiration time
|
||||
Lifetime: 11*time.Hour + 55*time.Minute,
|
||||
}
|
||||
if !p.actualProvider.Enabled() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
entry := p.actualProvider.Provide()[p.regionURL]
|
||||
return &entry
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider.Provide, creating dummy credentials.
|
||||
// Client code will call Provider.LazyProvide() at image pulling time.
|
||||
func (p *lazyEcrProvider) Provide() credentialprovider.DockerConfig {
|
||||
entry := credentialprovider.DockerConfigEntry{
|
||||
Provider: p,
|
||||
}
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
cfg[p.regionURL] = entry
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newEcrProvider(region string, getter tokenGetter) *ecrProvider {
|
||||
return &ecrProvider{
|
||||
region: region,
|
||||
regionURL: registryURL(region),
|
||||
getter: getter,
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled implements DockerConfigProvider.Enabled for the AWS token-based implementation.
|
||||
// For now, it gets activated only if AWS was chosen as the cloud provider.
|
||||
// TODO: figure how to enable it manually for deployments that are not on AWS but still
|
||||
// use ECR somehow?
|
||||
func (p *ecrProvider) Enabled() bool {
|
||||
if p.region == "" {
|
||||
glog.Errorf("Called ecrProvider.Enabled() with no region set")
|
||||
return false
|
||||
}
|
||||
|
||||
getter := &ecrTokenGetter{svc: ecr.New(session.New(&aws.Config{
|
||||
Credentials: nil,
|
||||
Region: &p.region,
|
||||
}))}
|
||||
getter.svc.Handlers.Sign.PushFrontNamed(request.NamedHandler{
|
||||
Name: "k8s/logger",
|
||||
Fn: awsHandlerLogger,
|
||||
})
|
||||
p.getter = getter
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider.LazyProvide. Should never be called.
|
||||
func (p *ecrProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider.Provide, refreshing ECR tokens on demand
|
||||
func (p *ecrProvider) Provide() credentialprovider.DockerConfig {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
|
||||
// TODO: fill in RegistryIds?
|
||||
params := &ecr.GetAuthorizationTokenInput{}
|
||||
output, err := p.getter.GetAuthorizationToken(params)
|
||||
if err != nil {
|
||||
glog.Errorf("while requesting ECR authorization token %v", err)
|
||||
return cfg
|
||||
}
|
||||
if output == nil {
|
||||
glog.Errorf("Got back no ECR token")
|
||||
return cfg
|
||||
}
|
||||
|
||||
for _, data := range output.AuthorizationData {
|
||||
if data.ProxyEndpoint != nil &&
|
||||
data.AuthorizationToken != nil {
|
||||
decodedToken, err := base64.StdEncoding.DecodeString(aws.StringValue(data.AuthorizationToken))
|
||||
if err != nil {
|
||||
glog.Errorf("while decoding token for endpoint %v %v", data.ProxyEndpoint, err)
|
||||
return cfg
|
||||
}
|
||||
parts := strings.SplitN(string(decodedToken), ":", 2)
|
||||
user := parts[0]
|
||||
password := parts[1]
|
||||
entry := credentialprovider.DockerConfigEntry{
|
||||
Username: user,
|
||||
Password: password,
|
||||
// ECR doesn't care and Docker is about to obsolete it
|
||||
Email: "not@val.id",
|
||||
}
|
||||
|
||||
glog.V(3).Infof("Adding credentials for user %s in %s", user, p.region)
|
||||
// Add our config entry for this region's registry URLs
|
||||
cfg[p.regionURL] = entry
|
||||
|
||||
}
|
||||
}
|
||||
return cfg
|
||||
}
|
163
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials_test.go
generated
vendored
Normal file
163
vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials_test.go
generated
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
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 credentials
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
const user = "foo"
|
||||
const password = "1234567890abcdef"
|
||||
const email = "not@val.id"
|
||||
|
||||
// Mock implementation
|
||||
type testTokenGetter struct {
|
||||
user string
|
||||
password string
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func (p *testTokenGetter) GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error) {
|
||||
|
||||
expiration := time.Now().Add(1 * time.Hour)
|
||||
creds := []byte(fmt.Sprintf("%s:%s", p.user, p.password))
|
||||
data := &ecr.AuthorizationData{
|
||||
AuthorizationToken: aws.String(base64.StdEncoding.EncodeToString(creds)),
|
||||
ExpiresAt: &expiration,
|
||||
ProxyEndpoint: aws.String(p.endpoint),
|
||||
}
|
||||
output := &ecr.GetAuthorizationTokenOutput{
|
||||
AuthorizationData: []*ecr.AuthorizationData{data},
|
||||
}
|
||||
|
||||
return output, nil //p.svc.GetAuthorizationToken(input)
|
||||
}
|
||||
|
||||
func TestEcrProvide(t *testing.T) {
|
||||
registry := "123456789012.dkr.ecr.lala-land-1.amazonaws.com"
|
||||
otherRegistries := []string{
|
||||
"123456789012.dkr.ecr.cn-foo-1.amazonaws.com.cn",
|
||||
"private.registry.com",
|
||||
"gcr.io",
|
||||
}
|
||||
image := "foo/bar"
|
||||
|
||||
provider := newEcrProvider("lala-land-1",
|
||||
&testTokenGetter{
|
||||
user: user,
|
||||
password: password,
|
||||
endpoint: registry,
|
||||
})
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
keyring.Add(provider.Provide())
|
||||
|
||||
// Verify that we get the expected username/password combo for
|
||||
// an ECR image name.
|
||||
fullImage := path.Join(registry, image)
|
||||
creds, ok := keyring.Lookup(fullImage)
|
||||
if !ok {
|
||||
t.Errorf("Didn't find expected URL: %s", fullImage)
|
||||
return
|
||||
}
|
||||
if len(creds) > 1 {
|
||||
t.Errorf("Got more hits than expected: %s", creds)
|
||||
}
|
||||
val := creds[0]
|
||||
|
||||
if user != val.Username {
|
||||
t.Errorf("Unexpected username value, want: _token, got: %s", 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)
|
||||
}
|
||||
|
||||
// Verify that we get an error for other images.
|
||||
for _, otherRegistry := range otherRegistries {
|
||||
fullImage = path.Join(otherRegistry, image)
|
||||
creds, ok = keyring.Lookup(fullImage)
|
||||
if ok {
|
||||
t.Errorf("Unexpectedly found image: %s", fullImage)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChinaEcrProvide(t *testing.T) {
|
||||
registry := "123456789012.dkr.ecr.cn-foo-1.amazonaws.com.cn"
|
||||
otherRegistries := []string{
|
||||
"123456789012.dkr.ecr.lala-land-1.amazonaws.com",
|
||||
"private.registry.com",
|
||||
"gcr.io",
|
||||
}
|
||||
image := "foo/bar"
|
||||
|
||||
provider := newEcrProvider("cn-foo-1",
|
||||
&testTokenGetter{
|
||||
user: user,
|
||||
password: password,
|
||||
endpoint: registry,
|
||||
})
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
keyring.Add(provider.Provide())
|
||||
|
||||
// Verify that we get the expected username/password combo for
|
||||
// an ECR image name.
|
||||
fullImage := path.Join(registry, image)
|
||||
creds, ok := keyring.Lookup(fullImage)
|
||||
if !ok {
|
||||
t.Errorf("Didn't find expected URL: %s", fullImage)
|
||||
return
|
||||
}
|
||||
if len(creds) > 1 {
|
||||
t.Errorf("Got more hits than expected: %s", creds)
|
||||
}
|
||||
val := creds[0]
|
||||
|
||||
if user != val.Username {
|
||||
t.Errorf("Unexpected username value, want: _token, got: %s", 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)
|
||||
}
|
||||
|
||||
// Verify that we get an error for other images.
|
||||
for _, otherRegistry := range otherRegistries {
|
||||
fullImage = path.Join(otherRegistry, image)
|
||||
creds, ok = keyring.Lookup(fullImage)
|
||||
if ok {
|
||||
t.Errorf("Unexpectedly found image: %s", fullImage)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
51
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/BUILD
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/BUILD
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"azure_acr_helper.go",
|
||||
"azure_credentials.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/azure",
|
||||
deps = [
|
||||
"//pkg/cloudprovider/providers/azure:go_default_library",
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/Azure/azure-sdk-for-go/arm/containerregistry:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
|
||||
"//vendor/github.com/dgrijalva/jwt-go:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["azure_credentials_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/azure",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/Azure/azure-sdk-for-go/arm/containerregistry:go_default_library",
|
||||
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
300
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/azure_acr_helper.go
generated
vendored
Normal file
300
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/azure_acr_helper.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2017 Microsoft Corporation
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
*/
|
||||
|
||||
// Source: https://github.com/Azure/acr-docker-credential-helper/blob/a79b541f3ee761f6cc4511863ed41fb038c19464/src/docker-credential-acr/acr_login.go
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
type authDirective struct {
|
||||
service string
|
||||
realm string
|
||||
}
|
||||
|
||||
type accessTokenPayload struct {
|
||||
TenantID string `json:"tid"`
|
||||
}
|
||||
|
||||
type acrTokenPayload struct {
|
||||
Expiration int64 `json:"exp"`
|
||||
TenantID string `json:"tenant"`
|
||||
Credential string `json:"credential"`
|
||||
}
|
||||
|
||||
type acrAuthResponse struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// 5 minutes buffer time to allow timeshift between local machine and AAD
|
||||
const timeShiftBuffer = 300
|
||||
const userAgentHeader = "User-Agent"
|
||||
const userAgent = "kubernetes-credentialprovider-acr"
|
||||
|
||||
const dockerTokenLoginUsernameGUID = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
var client = &http.Client{}
|
||||
|
||||
func receiveChallengeFromLoginServer(serverAddress string) (*authDirective, error) {
|
||||
challengeURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: serverAddress,
|
||||
Path: "v2/",
|
||||
}
|
||||
var err error
|
||||
var r *http.Request
|
||||
r, _ = http.NewRequest("GET", challengeURL.String(), nil)
|
||||
r.Header.Add(userAgentHeader, userAgent)
|
||||
|
||||
var challenge *http.Response
|
||||
if challenge, err = client.Do(r); err != nil {
|
||||
return nil, fmt.Errorf("Error reaching registry endpoint %s, error: %s", challengeURL.String(), err)
|
||||
}
|
||||
defer challenge.Body.Close()
|
||||
|
||||
if challenge.StatusCode != 401 {
|
||||
return nil, fmt.Errorf("Registry did not issue a valid AAD challenge, status: %d", challenge.StatusCode)
|
||||
}
|
||||
|
||||
var authHeader []string
|
||||
var ok bool
|
||||
if authHeader, ok = challenge.Header["Www-Authenticate"]; !ok {
|
||||
return nil, fmt.Errorf("Challenge response does not contain header 'Www-Authenticate'")
|
||||
}
|
||||
|
||||
if len(authHeader) != 1 {
|
||||
return nil, fmt.Errorf("Registry did not issue a valid AAD challenge, authenticate header [%s]",
|
||||
strings.Join(authHeader, ", "))
|
||||
}
|
||||
|
||||
authSections := strings.SplitN(authHeader[0], " ", 2)
|
||||
authType := strings.ToLower(authSections[0])
|
||||
var authParams *map[string]string
|
||||
if authParams, err = parseAssignments(authSections[1]); err != nil {
|
||||
return nil, fmt.Errorf("Unable to understand the contents of Www-Authenticate header %s", authSections[1])
|
||||
}
|
||||
|
||||
// verify headers
|
||||
if !strings.EqualFold("Bearer", authType) {
|
||||
return nil, fmt.Errorf("Www-Authenticate: expected realm: Bearer, actual: %s", authType)
|
||||
}
|
||||
if len((*authParams)["service"]) == 0 {
|
||||
return nil, fmt.Errorf("Www-Authenticate: missing header \"service\"")
|
||||
}
|
||||
if len((*authParams)["realm"]) == 0 {
|
||||
return nil, fmt.Errorf("Www-Authenticate: missing header \"realm\"")
|
||||
}
|
||||
|
||||
return &authDirective{
|
||||
service: (*authParams)["service"],
|
||||
realm: (*authParams)["realm"],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseAcrToken(identityToken string) (token *acrTokenPayload, err error) {
|
||||
tokenSegments := strings.Split(identityToken, ".")
|
||||
if len(tokenSegments) < 2 {
|
||||
return nil, fmt.Errorf("Invalid existing refresh token length: %d", len(tokenSegments))
|
||||
}
|
||||
payloadSegmentEncoded := tokenSegments[1]
|
||||
var payloadBytes []byte
|
||||
if payloadBytes, err = jwt.DecodeSegment(payloadSegmentEncoded); err != nil {
|
||||
return nil, fmt.Errorf("Error decoding payload segment from refresh token, error: %s", err)
|
||||
}
|
||||
var payload acrTokenPayload
|
||||
if err = json.Unmarshal(payloadBytes, &payload); err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshalling acr payload, error: %s", err)
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func performTokenExchange(
|
||||
serverAddress string,
|
||||
directive *authDirective,
|
||||
tenant string,
|
||||
accessToken string) (string, error) {
|
||||
var err error
|
||||
data := url.Values{
|
||||
"service": []string{directive.service},
|
||||
"grant_type": []string{"access_token_refresh_token"},
|
||||
"access_token": []string{accessToken},
|
||||
"refresh_token": []string{accessToken},
|
||||
"tenant": []string{tenant},
|
||||
}
|
||||
|
||||
var realmURL *url.URL
|
||||
if realmURL, err = url.Parse(directive.realm); err != nil {
|
||||
return "", fmt.Errorf("Www-Authenticate: invalid realm %s", directive.realm)
|
||||
}
|
||||
authEndpoint := fmt.Sprintf("%s://%s/oauth2/exchange", realmURL.Scheme, realmURL.Host)
|
||||
|
||||
datac := data.Encode()
|
||||
var r *http.Request
|
||||
r, _ = http.NewRequest("POST", authEndpoint, bytes.NewBufferString(datac))
|
||||
r.Header.Add(userAgentHeader, userAgent)
|
||||
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
r.Header.Add("Content-Length", strconv.Itoa(len(datac)))
|
||||
|
||||
var exchange *http.Response
|
||||
if exchange, err = client.Do(r); err != nil {
|
||||
return "", fmt.Errorf("Www-Authenticate: failed to reach auth url %s", authEndpoint)
|
||||
}
|
||||
|
||||
defer exchange.Body.Close()
|
||||
if exchange.StatusCode != 200 {
|
||||
return "", fmt.Errorf("Www-Authenticate: auth url %s responded with status code %d", authEndpoint, exchange.StatusCode)
|
||||
}
|
||||
|
||||
var content []byte
|
||||
if content, err = ioutil.ReadAll(exchange.Body); err != nil {
|
||||
return "", fmt.Errorf("Www-Authenticate: error reading response from %s", authEndpoint)
|
||||
}
|
||||
|
||||
var authResp acrAuthResponse
|
||||
if err = json.Unmarshal(content, &authResp); err != nil {
|
||||
return "", fmt.Errorf("Www-Authenticate: unable to read response %s", content)
|
||||
}
|
||||
|
||||
return authResp.RefreshToken, nil
|
||||
}
|
||||
|
||||
// Try and parse a string of assignments in the form of:
|
||||
// key1 = value1, key2 = "value 2", key3 = ""
|
||||
// Note: this method and handle quotes but does not handle escaping of quotes
|
||||
func parseAssignments(statements string) (*map[string]string, error) {
|
||||
var cursor int
|
||||
result := make(map[string]string)
|
||||
var errorMsg = fmt.Errorf("malformed header value: %s", statements)
|
||||
for {
|
||||
// parse key
|
||||
equalIndex := nextOccurrence(statements, cursor, "=")
|
||||
if equalIndex == -1 {
|
||||
return nil, errorMsg
|
||||
}
|
||||
key := strings.TrimSpace(statements[cursor:equalIndex])
|
||||
|
||||
// parse value
|
||||
cursor = nextNoneSpace(statements, equalIndex+1)
|
||||
if cursor == -1 {
|
||||
return nil, errorMsg
|
||||
}
|
||||
// case: value is quoted
|
||||
if statements[cursor] == '"' {
|
||||
cursor = cursor + 1
|
||||
// like I said, not handling escapes, but this will skip any comma that's
|
||||
// within the quotes which is somewhat more likely
|
||||
closeQuoteIndex := nextOccurrence(statements, cursor, "\"")
|
||||
if closeQuoteIndex == -1 {
|
||||
return nil, errorMsg
|
||||
}
|
||||
value := statements[cursor:closeQuoteIndex]
|
||||
result[key] = value
|
||||
|
||||
commaIndex := nextNoneSpace(statements, closeQuoteIndex+1)
|
||||
if commaIndex == -1 {
|
||||
// no more comma, done
|
||||
return &result, nil
|
||||
} else if statements[commaIndex] != ',' {
|
||||
// expect comma immediately after close quote
|
||||
return nil, errorMsg
|
||||
} else {
|
||||
cursor = commaIndex + 1
|
||||
}
|
||||
} else {
|
||||
commaIndex := nextOccurrence(statements, cursor, ",")
|
||||
endStatements := commaIndex == -1
|
||||
var untrimmed string
|
||||
if endStatements {
|
||||
untrimmed = statements[cursor:commaIndex]
|
||||
} else {
|
||||
untrimmed = statements[cursor:]
|
||||
}
|
||||
value := strings.TrimSpace(untrimmed)
|
||||
|
||||
if len(value) == 0 {
|
||||
// disallow empty value without quote
|
||||
return nil, errorMsg
|
||||
}
|
||||
|
||||
result[key] = value
|
||||
|
||||
if endStatements {
|
||||
return &result, nil
|
||||
}
|
||||
cursor = commaIndex + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nextOccurrence(str string, start int, sep string) int {
|
||||
if start >= len(str) {
|
||||
return -1
|
||||
}
|
||||
offset := strings.Index(str[start:], sep)
|
||||
if offset == -1 {
|
||||
return -1
|
||||
}
|
||||
return offset + start
|
||||
}
|
||||
|
||||
func nextNoneSpace(str string, start int) int {
|
||||
if start >= len(str) {
|
||||
return -1
|
||||
}
|
||||
offset := strings.IndexFunc(str[start:], func(c rune) bool { return !unicode.IsSpace(c) })
|
||||
if offset == -1 {
|
||||
return -1
|
||||
}
|
||||
return offset + start
|
||||
}
|
175
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/azure_credentials.go
generated
vendored
Normal file
175
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/azure_credentials.go
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright 2016 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 azure
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
azureapi "github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
var flagConfigFile = pflag.String("azure-container-registry-config", "",
|
||||
"Path to the file container Azure container registry configuration information.")
|
||||
|
||||
const dummyRegistryEmail = "name@contoso.com"
|
||||
|
||||
// init registers the various means by which credentials may
|
||||
// be resolved on Azure.
|
||||
func init() {
|
||||
credentialprovider.RegisterCredentialProvider("azure",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: NewACRProvider(flagConfigFile),
|
||||
Lifetime: 1 * time.Minute,
|
||||
})
|
||||
}
|
||||
|
||||
// RegistriesClient is a testable interface for the ACR client List operation.
|
||||
type RegistriesClient interface {
|
||||
List() (containerregistry.RegistryListResult, error)
|
||||
}
|
||||
|
||||
// NewACRProvider parses the specified configFile and returns a DockerConfigProvider
|
||||
func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider {
|
||||
return &acrProvider{
|
||||
file: configFile,
|
||||
}
|
||||
}
|
||||
|
||||
type acrProvider struct {
|
||||
file *string
|
||||
config *azure.Config
|
||||
environment *azureapi.Environment
|
||||
registryClient RegistriesClient
|
||||
servicePrincipalToken *adal.ServicePrincipalToken
|
||||
}
|
||||
|
||||
func (a *acrProvider) loadConfig(rdr io.Reader) error {
|
||||
var err error
|
||||
a.config, a.environment, err = azure.ParseConfig(rdr)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to load azure credential file: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *acrProvider) Enabled() bool {
|
||||
if a.file == nil || len(*a.file) == 0 {
|
||||
glog.V(5).Infof("Azure config unspecified, disabling")
|
||||
return false
|
||||
}
|
||||
|
||||
f, err := os.Open(*a.file)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to load config from file: %s", *a.file)
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = a.loadConfig(f)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to load config from file: %s", *a.file)
|
||||
return false
|
||||
}
|
||||
|
||||
a.servicePrincipalToken, err = azure.GetServicePrincipalToken(a.config, a.environment)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to create service principal token: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
registryClient := containerregistry.NewRegistriesClient(a.config.SubscriptionID)
|
||||
registryClient.BaseURI = a.environment.ResourceManagerEndpoint
|
||||
registryClient.Authorizer = autorest.NewBearerAuthorizer(a.servicePrincipalToken)
|
||||
a.registryClient = registryClient
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *acrProvider) Provide() credentialprovider.DockerConfig {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
|
||||
glog.V(4).Infof("listing registries")
|
||||
res, err := a.registryClient.List()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to list registries: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
for ix := range *res.Value {
|
||||
loginServer := getLoginServer((*res.Value)[ix])
|
||||
var cred *credentialprovider.DockerConfigEntry
|
||||
|
||||
if a.config.UseManagedIdentityExtension {
|
||||
cred, err = getACRDockerEntryFromARMToken(a, loginServer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
cred = &credentialprovider.DockerConfigEntry{
|
||||
Username: a.config.AADClientID,
|
||||
Password: a.config.AADClientSecret,
|
||||
Email: dummyRegistryEmail,
|
||||
}
|
||||
}
|
||||
|
||||
cfg[loginServer] = *cred
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getLoginServer(registry containerregistry.Registry) string {
|
||||
return *(*registry.RegistryProperties).LoginServer
|
||||
}
|
||||
|
||||
func getACRDockerEntryFromARMToken(a *acrProvider, loginServer string) (*credentialprovider.DockerConfigEntry, error) {
|
||||
armAccessToken := a.servicePrincipalToken.AccessToken
|
||||
|
||||
glog.V(4).Infof("discovering auth redirects for: %s", loginServer)
|
||||
directive, err := receiveChallengeFromLoginServer(loginServer)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to receive challenge: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("exchanging an acr refresh_token")
|
||||
registryRefreshToken, err := performTokenExchange(
|
||||
loginServer, directive, a.config.TenantID, armAccessToken)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to perform token exchange: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("adding ACR docker config entry for: %s", loginServer)
|
||||
return &credentialprovider.DockerConfigEntry{
|
||||
Username: dockerTokenLoginUsernameGUID,
|
||||
Password: registryRefreshToken,
|
||||
Email: dummyRegistryEmail,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *acrProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
91
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/azure_credentials_test.go
generated
vendored
Normal file
91
vendor/k8s.io/kubernetes/pkg/credentialprovider/azure/azure_credentials_test.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2016 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 azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
results containerregistry.RegistryListResult
|
||||
}
|
||||
|
||||
func (f *fakeClient) List() (containerregistry.RegistryListResult, error) {
|
||||
return f.results, nil
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
configStr := `
|
||||
{
|
||||
"aadClientId": "foo",
|
||||
"aadClientSecret": "bar"
|
||||
}`
|
||||
result := containerregistry.RegistryListResult{
|
||||
Value: &[]containerregistry.Registry{
|
||||
{
|
||||
Name: to.StringPtr("foo"),
|
||||
RegistryProperties: &containerregistry.RegistryProperties{
|
||||
LoginServer: to.StringPtr("foo-microsoft.azurecr.io"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: to.StringPtr("bar"),
|
||||
RegistryProperties: &containerregistry.RegistryProperties{
|
||||
LoginServer: to.StringPtr("bar-microsoft.azurecr.io"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: to.StringPtr("baz"),
|
||||
RegistryProperties: &containerregistry.RegistryProperties{
|
||||
LoginServer: to.StringPtr("baz-microsoft.azurecr.io"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeClient := &fakeClient{
|
||||
results: result,
|
||||
}
|
||||
|
||||
provider := &acrProvider{
|
||||
registryClient: fakeClient,
|
||||
}
|
||||
provider.loadConfig(bytes.NewBufferString(configStr))
|
||||
|
||||
creds := provider.Provide()
|
||||
|
||||
if len(creds) != len(*result.Value) {
|
||||
t.Errorf("Unexpected list: %v, expected length %d", creds, len(*result.Value))
|
||||
}
|
||||
for _, cred := range creds {
|
||||
if cred.Username != "foo" {
|
||||
t.Errorf("expected 'foo' for username, saw: %v", cred.Username)
|
||||
}
|
||||
if cred.Password != "bar" {
|
||||
t.Errorf("expected 'bar' for password, saw: %v", cred.Username)
|
||||
}
|
||||
}
|
||||
for _, val := range *result.Value {
|
||||
registryName := getLoginServer(val)
|
||||
if _, found := creds[registryName]; !found {
|
||||
t.Errorf("Missing expected registry: %s", registryName)
|
||||
}
|
||||
}
|
||||
}
|
295
vendor/k8s.io/kubernetes/pkg/credentialprovider/config.go
generated
vendored
Normal file
295
vendor/k8s.io/kubernetes/pkg/credentialprovider/config.go
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
type DockerConfigEntry struct {
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
Provider DockerConfigProvider
|
||||
}
|
||||
|
||||
var (
|
||||
preferredPathLock sync.Mutex
|
||||
preferredPath = ""
|
||||
workingDirPath = ""
|
||||
homeDirPath = os.Getenv("HOME")
|
||||
rootDirPath = "/"
|
||||
homeJsonDirPath = filepath.Join(homeDirPath, ".docker")
|
||||
rootJsonDirPath = filepath.Join(rootDirPath, ".docker")
|
||||
|
||||
configFileName = ".dockercfg"
|
||||
configJsonFileName = "config.json"
|
||||
)
|
||||
|
||||
func SetPreferredDockercfgPath(path string) {
|
||||
preferredPathLock.Lock()
|
||||
defer preferredPathLock.Unlock()
|
||||
preferredPath = 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 {
|
||||
glog.Errorf("while trying to canonicalize %s: %v", configPath, err)
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("looking for .dockercfg at %s", absDockerConfigFileLocation)
|
||||
contents, err := ioutil.ReadFile(absDockerConfigFileLocation)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
glog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
|
||||
continue
|
||||
}
|
||||
cfg, err := readDockerConfigFileFromBytes(contents)
|
||||
if err == nil {
|
||||
glog.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 {
|
||||
glog.Errorf("while trying to canonicalize %s: %v", configPath, err)
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("looking for %s at %s", configJsonFileName, absDockerConfigFileLocation)
|
||||
cfg, err = ReadSpecificDockerConfigJsonFile(absDockerConfigFileLocation)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
glog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
glog.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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
glog.V(2).Infof("body of failing http response: %v", resp.Body)
|
||||
return nil, &HttpError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Url: url,
|
||||
}
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
func ReadDockerConfigFileFromUrl(url string, client *http.Client, header *http.Header) (cfg DockerConfig, err error) {
|
||||
if contents, err := ReadUrl(url, client, header); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return readDockerConfigFileFromBytes(contents)
|
||||
}
|
||||
}
|
||||
|
||||
func readDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
|
||||
if err = json.Unmarshal(contents, &cfg); err != nil {
|
||||
glog.Errorf("while trying to parse blob %q: %v", contents, err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readDockerConfigJsonFileFromBytes(contents []byte) (cfg DockerConfig, err error) {
|
||||
var cfgJson DockerConfigJson
|
||||
if err = json.Unmarshal(contents, &cfgJson); err != nil {
|
||||
glog.Errorf("while trying to parse blob %q: %v", contents, err)
|
||||
return nil, err
|
||||
}
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(field)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("unable to parse auth field")
|
||||
return
|
||||
}
|
||||
|
||||
username = parts[0]
|
||||
password = parts[1]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func encodeDockerConfigFieldAuth(username, password string) string {
|
||||
fieldValue := username + ":" + password
|
||||
|
||||
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
|
||||
}
|
266
vendor/k8s.io/kubernetes/pkg/credentialprovider/config_test.go
generated
vendored
Normal file
266
vendor/k8s.io/kubernetes/pkg/credentialprovider/config_test.go
generated
vendored
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
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/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadDockerConfigFile(t *testing.T) {
|
||||
configJsonFileName := "config.json"
|
||||
var fileInfo *os.File
|
||||
preferredPaths := []string{}
|
||||
|
||||
//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)
|
||||
preferredPaths = append(preferredPaths, 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) {
|
||||
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) {
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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",
|
||||
},
|
||||
|
||||
// good base64 data, but no colon separating username & password
|
||||
{
|
||||
input: "cGFudHM=",
|
||||
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
|
||||
{
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
19
vendor/k8s.io/kubernetes/pkg/credentialprovider/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/credentialprovider/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 supplies interfaces and implementations for
|
||||
// docker registry providers to expose their authentication scheme.
|
||||
package credentialprovider // import "k8s.io/kubernetes/pkg/credentialprovider"
|
53
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/BUILD
generated
vendored
Normal file
53
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/BUILD
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"jwt.go",
|
||||
"metadata.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/gcp",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/golang.org/x/oauth2:go_default_library",
|
||||
"//vendor/golang.org/x/oauth2/google:go_default_library",
|
||||
"//vendor/golang.org/x/oauth2/jwt:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"jwt_test.go",
|
||||
"metadata_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/gcp",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
12
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/OWNERS
generated
vendored
Executable file
12
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/OWNERS
generated
vendored
Executable file
@ -0,0 +1,12 @@
|
||||
reviewers:
|
||||
- thockin
|
||||
- deads2k
|
||||
- yujuhong
|
||||
- derekwaynecarr
|
||||
- vishh
|
||||
- mikedanese
|
||||
- davidopp
|
||||
- eparis
|
||||
- dims
|
||||
- david-mcmahon
|
||||
- therc
|
19
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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_credentials contains implementations of DockerConfigProvider
|
||||
// for Google Cloud Platform.
|
||||
package gcp_credentials // import "k8s.io/kubernetes/pkg/credentialprovider/gcp"
|
116
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/jwt.go
generated
vendored
Normal file
116
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/jwt.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
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_credentials
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
storageReadOnlyScope = "https://www.googleapis.com/auth/devstorage.read_only"
|
||||
)
|
||||
|
||||
var (
|
||||
flagJwtFile = pflag.String("google-json-key", "",
|
||||
"The Google Cloud Platform Service Account JSON Key to use for authentication.")
|
||||
)
|
||||
|
||||
// A DockerConfigProvider that reads its configuration from Google
|
||||
// Compute Engine metadata.
|
||||
type jwtProvider struct {
|
||||
path *string
|
||||
config *jwt.Config
|
||||
tokenUrl string
|
||||
}
|
||||
|
||||
// init registers the various means by which credentials may
|
||||
// be resolved on GCP.
|
||||
func init() {
|
||||
credentialprovider.RegisterCredentialProvider("google-jwt-key",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: &jwtProvider{
|
||||
path: flagJwtFile,
|
||||
},
|
||||
Lifetime: 30 * time.Minute,
|
||||
})
|
||||
}
|
||||
|
||||
// Enabled implements DockerConfigProvider for the JSON Key based implementation.
|
||||
func (j *jwtProvider) Enabled() bool {
|
||||
if *j.path == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(*j.path)
|
||||
if err != nil {
|
||||
glog.Errorf("while reading file %s got %v", *j.path, err)
|
||||
return false
|
||||
}
|
||||
config, err := google.JWTConfigFromJSON(data, storageReadOnlyScope)
|
||||
if err != nil {
|
||||
glog.Errorf("while parsing %s data got %v", *j.path, err)
|
||||
return false
|
||||
}
|
||||
|
||||
j.config = config
|
||||
if j.tokenUrl != "" {
|
||||
j.config.TokenURL = j.tokenUrl
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider. Should never be called.
|
||||
func (j *jwtProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (j *jwtProvider) Provide() credentialprovider.DockerConfig {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
|
||||
ts := j.config.TokenSource(oauth2.NoContext)
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
glog.Errorf("while exchanging json key %s for access token %v", *j.path, err)
|
||||
return cfg
|
||||
}
|
||||
if !token.Valid() {
|
||||
glog.Errorf("Got back invalid token: %v", token)
|
||||
return cfg
|
||||
}
|
||||
|
||||
entry := credentialprovider.DockerConfigEntry{
|
||||
Username: "_token",
|
||||
Password: token.AccessToken,
|
||||
Email: j.config.Email,
|
||||
}
|
||||
|
||||
// Add our entry for each of the supported container registry URLs
|
||||
for _, k := range containerRegistryUrls {
|
||||
cfg[k] = entry
|
||||
}
|
||||
return cfg
|
||||
}
|
127
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/jwt_test.go
generated
vendored
Normal file
127
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/jwt_test.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
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_credentials
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
const email = "foo@bar.com"
|
||||
|
||||
// From oauth2/jwt_test.go
|
||||
var (
|
||||
dummyPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE
|
||||
DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY
|
||||
fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK
|
||||
1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr
|
||||
k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9
|
||||
/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt
|
||||
3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn
|
||||
2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3
|
||||
nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK
|
||||
6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf
|
||||
5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e
|
||||
DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1
|
||||
M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g
|
||||
z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y
|
||||
1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK
|
||||
J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U
|
||||
f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx
|
||||
QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA
|
||||
cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr
|
||||
Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw
|
||||
5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg
|
||||
KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84
|
||||
OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd
|
||||
mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ
|
||||
5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg==
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
jsonKey = fmt.Sprintf(`{"private_key":"%[1]s", "client_email":"%[2]s", "type": "service_account"}`,
|
||||
strings.Replace(dummyPrivateKey, "\n", "\\n", -1), email)
|
||||
)
|
||||
|
||||
func TestJwtProvider(t *testing.T) {
|
||||
token := "asdhflkjsdfkjhsdf"
|
||||
|
||||
// Modeled after oauth2/jwt_test.go
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(fmt.Sprintf(`{
|
||||
"access_token": "%[1]s",
|
||||
"scope": "user",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600
|
||||
}`, token)))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
file, err := ioutil.TempFile(os.TempDir(), "temp")
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating temp file: %v", err)
|
||||
}
|
||||
filename := file.Name()
|
||||
defer os.Remove(filename)
|
||||
|
||||
_, err = file.WriteString(jsonKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing temp file: %v", err)
|
||||
}
|
||||
|
||||
provider := &jwtProvider{
|
||||
path: &filename,
|
||||
tokenUrl: ts.URL,
|
||||
}
|
||||
if !provider.Enabled() {
|
||||
t.Fatalf("Provider is unexpectedly disabled")
|
||||
}
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
keyring.Add(provider.Provide())
|
||||
|
||||
// Verify that we get the expected username/password combo for
|
||||
// a gcr.io image name.
|
||||
registryUrl := "gcr.io/foo/bar"
|
||||
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 "_token" != val.Username {
|
||||
t.Errorf("Unexpected username value, want: _token, got: %s", val.Username)
|
||||
}
|
||||
if token != val.Password {
|
||||
t.Errorf("Unexpected password value, want: %s, got: %s", token, val.Password)
|
||||
}
|
||||
if email != val.Email {
|
||||
t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
|
||||
}
|
||||
}
|
298
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata.go
generated
vendored
Normal file
298
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata.go
generated
vendored
Normal file
@ -0,0 +1,298 @@
|
||||
/*
|
||||
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_credentials
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
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"
|
||||
googleProductName = "Google"
|
||||
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"}
|
||||
|
||||
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() {
|
||||
tr := utilnet.SetTransportDefaults(&http.Transport{})
|
||||
metadataHTTPClientTimeout := time.Second * 10
|
||||
httpClient := &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: metadataHTTPClientTimeout,
|
||||
}
|
||||
credentialprovider.RegisterCredentialProvider("google-dockercfg",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: &dockerConfigKeyProvider{
|
||||
metadataProvider{Client: httpClient},
|
||||
},
|
||||
Lifetime: 60 * time.Second,
|
||||
})
|
||||
|
||||
credentialprovider.RegisterCredentialProvider("google-dockercfg-url",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: &dockerConfigUrlKeyProvider{
|
||||
metadataProvider{Client: httpClient},
|
||||
},
|
||||
Lifetime: 60 * time.Second,
|
||||
})
|
||||
|
||||
credentialprovider.RegisterCredentialProvider("google-container-registry",
|
||||
// Never cache this. The access token is already
|
||||
// cached by the metadata service.
|
||||
&containerRegistryProvider{
|
||||
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 {
|
||||
data, err := ioutil.ReadFile(gceProductNameFile)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Error while reading product_name: %v", err)
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(data), googleProductName)
|
||||
}
|
||||
|
||||
// Enabled implements DockerConfigProvider for all of the Google implementations.
|
||||
func (g *metadataProvider) Enabled() bool {
|
||||
return onGCEVM()
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider. Should never be called.
|
||||
func (g *dockerConfigKeyProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *dockerConfigKeyProvider) Provide() 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 {
|
||||
glog.Errorf("while reading 'google-dockercfg' metadata: %v", err)
|
||||
} else {
|
||||
return cfg
|
||||
}
|
||||
|
||||
return credentialprovider.DockerConfig{}
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider. Should never be called.
|
||||
func (g *dockerConfigUrlKeyProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *dockerConfigUrlKeyProvider) Provide() 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 {
|
||||
glog.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 {
|
||||
glog.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://)
|
||||
glog.Errorf("Unsupported URL scheme: %s", string(url))
|
||||
}
|
||||
}
|
||||
|
||||
return credentialprovider.DockerConfig{}
|
||||
}
|
||||
|
||||
// runcWithBackoff 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 {
|
||||
glog.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 {
|
||||
glog.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 {
|
||||
glog.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 {
|
||||
glog.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
|
||||
}
|
||||
}
|
||||
glog.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"`
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider. Should never be called.
|
||||
func (g *containerRegistryProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider
|
||||
func (g *containerRegistryProvider) Provide() credentialprovider.DockerConfig {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
|
||||
tokenJsonBlob, err := credentialprovider.ReadUrl(metadataToken, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
glog.Errorf("while reading access token endpoint: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
email, err := credentialprovider.ReadUrl(metadataEmail, g.Client, metadataHeader)
|
||||
if err != nil {
|
||||
glog.Errorf("while reading email endpoint: %v", err)
|
||||
return cfg
|
||||
}
|
||||
|
||||
var parsedBlob tokenBlob
|
||||
if err := json.Unmarshal([]byte(tokenJsonBlob), &parsedBlob); err != nil {
|
||||
glog.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
|
||||
}
|
441
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata_test.go
generated
vendored
Normal file
441
vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata_test.go
generated
vendored
Normal file
@ -0,0 +1,441 @@
|
||||
/*
|
||||
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_credentials
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestDockerKeyringFromGoogleDockerConfigMetadata(t *testing.T) {
|
||||
registryUrl := "hello.kubernetes.io"
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"https://%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, registryUrl, email, auth)
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(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) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintln(w, sampleDockerConfig)
|
||||
} else {
|
||||
w.WriteHeader(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{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 TestDockerKeyringFromGoogleDockerConfigMetadataUrl(t *testing.T) {
|
||||
registryUrl := "hello.kubernetes.io"
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"https://%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, registryUrl, email, auth)
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
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(dockerConfigUrlKey, r.URL.Path) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/text")
|
||||
fmt.Fprint(w, "http://foo.bar.com"+valueEndpoint)
|
||||
} else {
|
||||
w.WriteHeader(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{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 TestContainerRegistryBasics(t *testing.T) {
|
||||
registryUrl := "container.cloud.google.com"
|
||||
email := "1234@project.gserviceaccount.com"
|
||||
token := &tokenBlob{AccessToken: "ya26.lots-of-indiscernible-garbage"}
|
||||
|
||||
const (
|
||||
serviceAccountsEndpoint = "/computeMetadata/v1/instance/service-accounts/"
|
||||
defaultEndpoint = "/computeMetadata/v1/instance/service-accounts/default/"
|
||||
scopeEndpoint = defaultEndpoint + "scopes"
|
||||
emailEndpoint = defaultEndpoint + "email"
|
||||
tokenEndpoint = defaultEndpoint + "token"
|
||||
)
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(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)
|
||||
} 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 {
|
||||
w.WriteHeader(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{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 "_token" != val.Username {
|
||||
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 TestContainerRegistryNoServiceAccount(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 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
|
||||
// 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{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if provider.Enabled() {
|
||||
t.Errorf("Provider is unexpectedly enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerRegistryNoStorageScope(t *testing.T) {
|
||||
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 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
|
||||
// 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{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if provider.Enabled() {
|
||||
t.Errorf("Provider is unexpectedly enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputePlatformScopeSubstitutesStorageScope(t *testing.T) {
|
||||
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 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
var err error
|
||||
gceProductNameFile, err = createProductNameFile()
|
||||
if err != nil {
|
||||
t.Errorf("failed to create gce product name file: %v", err)
|
||||
}
|
||||
defer os.Remove(gceProductNameFile)
|
||||
|
||||
// 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{Client: &http.Client{Transport: transport}},
|
||||
}
|
||||
|
||||
if !provider.Enabled() {
|
||||
t.Errorf("Provider is unexpectedly disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllProvidersNoMetadata(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(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{Client: &http.Client{Transport: transport}},
|
||||
},
|
||||
&dockerConfigUrlKeyProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
},
|
||||
&containerRegistryProvider{
|
||||
metadataProvider{Client: &http.Client{Transport: transport}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, provider := range providers {
|
||||
if provider.Enabled() {
|
||||
t.Errorf("Provider %s is unexpectedly enabled", reflect.TypeOf(provider).String())
|
||||
}
|
||||
}
|
||||
}
|
338
vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring.go
generated
vendored
Normal file
338
vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring.go
generated
vendored
Normal file
@ -0,0 +1,338 @@
|
||||
/*
|
||||
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/json"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// DockerKeyring tracks a set of docker registry credentials, maintaining a
|
||||
// reverse index across the registry endpoints. A registry endpoint is made
|
||||
// up of a host (e.g. registry.example.com), but it may also contain a path
|
||||
// (e.g. registry.example.com/foo) This index is important for two reasons:
|
||||
// - registry endpoints may overlap, and when this happens we must find the
|
||||
// most specific match for a given image
|
||||
// - iterating a map does not yield predictable results
|
||||
type DockerKeyring interface {
|
||||
Lookup(image string) ([]LazyAuthConfiguration, bool)
|
||||
}
|
||||
|
||||
// BasicDockerKeyring is a trivial map-backed implementation of DockerKeyring
|
||||
type BasicDockerKeyring struct {
|
||||
index []string
|
||||
creds map[string][]LazyAuthConfiguration
|
||||
}
|
||||
|
||||
// lazyDockerKeyring is an implementation of DockerKeyring that lazily
|
||||
// materializes its dockercfg based on a set of dockerConfigProviders.
|
||||
type lazyDockerKeyring struct {
|
||||
Providers []DockerConfigProvider
|
||||
}
|
||||
|
||||
// LazyAuthConfiguration wraps dockertypes.AuthConfig, potentially deferring its
|
||||
// binding. If Provider is non-nil, it will be used to obtain new credentials
|
||||
// by calling LazyProvide() on it.
|
||||
type LazyAuthConfiguration struct {
|
||||
dockertypes.AuthConfig
|
||||
Provider DockerConfigProvider
|
||||
}
|
||||
|
||||
func DockerConfigEntryToLazyAuthConfiguration(ident DockerConfigEntry) LazyAuthConfiguration {
|
||||
return LazyAuthConfiguration{
|
||||
AuthConfig: dockertypes.AuthConfig{
|
||||
Username: ident.Username,
|
||||
Password: ident.Password,
|
||||
Email: ident.Email,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (dk *BasicDockerKeyring) Add(cfg DockerConfig) {
|
||||
if dk.index == nil {
|
||||
dk.index = make([]string, 0)
|
||||
dk.creds = make(map[string][]LazyAuthConfiguration)
|
||||
}
|
||||
for loc, ident := range cfg {
|
||||
|
||||
var creds LazyAuthConfiguration
|
||||
if ident.Provider != nil {
|
||||
creds = LazyAuthConfiguration{
|
||||
Provider: ident.Provider,
|
||||
}
|
||||
} else {
|
||||
creds = DockerConfigEntryToLazyAuthConfiguration(ident)
|
||||
}
|
||||
|
||||
value := loc
|
||||
if !strings.HasPrefix(value, "https://") && !strings.HasPrefix(value, "http://") {
|
||||
value = "https://" + value
|
||||
}
|
||||
parsed, err := url.Parse(value)
|
||||
if err != nil {
|
||||
glog.Errorf("Entry %q in dockercfg invalid (%v), ignoring", loc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// The docker client allows exact matches:
|
||||
// foo.bar.com/namespace
|
||||
// Or hostname matches:
|
||||
// foo.bar.com
|
||||
// It also considers /v2/ and /v1/ equivalent to the hostname
|
||||
// See ResolveAuthConfig in docker/registry/auth.go.
|
||||
effectivePath := parsed.Path
|
||||
if strings.HasPrefix(effectivePath, "/v2/") || strings.HasPrefix(effectivePath, "/v1/") {
|
||||
effectivePath = effectivePath[3:]
|
||||
}
|
||||
var key string
|
||||
if (len(effectivePath) > 0) && (effectivePath != "/") {
|
||||
key = parsed.Host + effectivePath
|
||||
} else {
|
||||
key = parsed.Host
|
||||
}
|
||||
dk.creds[key] = append(dk.creds[key], creds)
|
||||
dk.index = append(dk.index, key)
|
||||
}
|
||||
|
||||
eliminateDupes := sets.NewString(dk.index...)
|
||||
dk.index = eliminateDupes.List()
|
||||
|
||||
// Update the index used to identify which credentials to use for a given
|
||||
// image. The index is reverse-sorted so more specific paths are matched
|
||||
// first. For example, if for the given image "quay.io/coreos/etcd",
|
||||
// credentials for "quay.io/coreos" should match before "quay.io".
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(dk.index)))
|
||||
}
|
||||
|
||||
const (
|
||||
defaultRegistryHost = "index.docker.io"
|
||||
defaultRegistryKey = defaultRegistryHost + "/v1/"
|
||||
)
|
||||
|
||||
// isDefaultRegistryMatch determines whether the given image will
|
||||
// pull from the default registry (DockerHub) based on the
|
||||
// characteristics of its name.
|
||||
func isDefaultRegistryMatch(image string) bool {
|
||||
parts := strings.SplitN(image, "/", 2)
|
||||
|
||||
if len(parts[0]) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
// e.g. library/ubuntu
|
||||
return true
|
||||
}
|
||||
|
||||
if parts[0] == "docker.io" || parts[0] == "index.docker.io" {
|
||||
// resolve docker.io/image and index.docker.io/image as default registry
|
||||
return true
|
||||
}
|
||||
|
||||
// From: http://blog.docker.com/2013/07/how-to-use-your-own-registry/
|
||||
// Docker looks for either a “.” (domain separator) or “:” (port separator)
|
||||
// to learn that the first part of the repository name is a location and not
|
||||
// a user name.
|
||||
return !strings.ContainsAny(parts[0], ".:")
|
||||
}
|
||||
|
||||
// url.Parse require a scheme, but ours don't have schemes. Adding a
|
||||
// scheme to make url.Parse happy, then clear out the resulting scheme.
|
||||
func parseSchemelessUrl(schemelessUrl string) (*url.URL, error) {
|
||||
parsed, err := url.Parse("https://" + schemelessUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// clear out the resulting scheme
|
||||
parsed.Scheme = ""
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// split the host name into parts, as well as the port
|
||||
func splitUrl(url *url.URL) (parts []string, port string) {
|
||||
host, port, err := net.SplitHostPort(url.Host)
|
||||
if err != nil {
|
||||
// could not parse port
|
||||
host, port = url.Host, ""
|
||||
}
|
||||
return strings.Split(host, "."), port
|
||||
}
|
||||
|
||||
// overloaded version of urlsMatch, operating on strings instead of URLs.
|
||||
func urlsMatchStr(glob string, target string) (bool, error) {
|
||||
globUrl, err := parseSchemelessUrl(glob)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
targetUrl, err := parseSchemelessUrl(target)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return urlsMatch(globUrl, targetUrl)
|
||||
}
|
||||
|
||||
// check whether the given target url matches the glob url, which may have
|
||||
// glob wild cards in the host name.
|
||||
//
|
||||
// Examples:
|
||||
// globUrl=*.docker.io, targetUrl=blah.docker.io => match
|
||||
// globUrl=*.docker.io, targetUrl=not.right.io => no match
|
||||
//
|
||||
// Note that we don't support wildcards in ports and paths yet.
|
||||
func urlsMatch(globUrl *url.URL, targetUrl *url.URL) (bool, error) {
|
||||
globUrlParts, globPort := splitUrl(globUrl)
|
||||
targetUrlParts, targetPort := splitUrl(targetUrl)
|
||||
if globPort != targetPort {
|
||||
// port doesn't match
|
||||
return false, nil
|
||||
}
|
||||
if len(globUrlParts) != len(targetUrlParts) {
|
||||
// host name does not have the same number of parts
|
||||
return false, nil
|
||||
}
|
||||
if !strings.HasPrefix(targetUrl.Path, globUrl.Path) {
|
||||
// the path of the credential must be a prefix
|
||||
return false, nil
|
||||
}
|
||||
for k, globUrlPart := range globUrlParts {
|
||||
targetUrlPart := targetUrlParts[k]
|
||||
matched, err := filepath.Match(globUrlPart, targetUrlPart)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !matched {
|
||||
// glob mismatch for some part
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
// everything matches
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Lookup implements the DockerKeyring method for fetching credentials based on image name.
|
||||
// Multiple credentials may be returned if there are multiple potentially valid credentials
|
||||
// available. This allows for rotation.
|
||||
func (dk *BasicDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
|
||||
// range over the index as iterating over a map does not provide a predictable ordering
|
||||
ret := []LazyAuthConfiguration{}
|
||||
for _, k := range dk.index {
|
||||
// both k and image are schemeless URLs because even though schemes are allowed
|
||||
// in the credential configurations, we remove them in Add.
|
||||
if matched, _ := urlsMatchStr(k, image); !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, dk.creds[k]...)
|
||||
}
|
||||
|
||||
if len(ret) > 0 {
|
||||
return ret, true
|
||||
}
|
||||
|
||||
// Use credentials for the default registry if provided, and appropriate
|
||||
if isDefaultRegistryMatch(image) {
|
||||
if auth, ok := dk.creds[defaultRegistryHost]; ok {
|
||||
return auth, true
|
||||
}
|
||||
}
|
||||
|
||||
return []LazyAuthConfiguration{}, false
|
||||
}
|
||||
|
||||
// Lookup implements the DockerKeyring method for fetching credentials
|
||||
// based on image name.
|
||||
func (dk *lazyDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
|
||||
keyring := &BasicDockerKeyring{}
|
||||
|
||||
for _, p := range dk.Providers {
|
||||
keyring.Add(p.Provide())
|
||||
}
|
||||
|
||||
return keyring.Lookup(image)
|
||||
}
|
||||
|
||||
type FakeKeyring struct {
|
||||
auth []LazyAuthConfiguration
|
||||
ok bool
|
||||
}
|
||||
|
||||
func (f *FakeKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
|
||||
return f.auth, f.ok
|
||||
}
|
||||
|
||||
// unionDockerKeyring delegates to a set of keyrings.
|
||||
type unionDockerKeyring struct {
|
||||
keyrings []DockerKeyring
|
||||
}
|
||||
|
||||
func (k *unionDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
|
||||
authConfigs := []LazyAuthConfiguration{}
|
||||
for _, subKeyring := range k.keyrings {
|
||||
if subKeyring == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
currAuthResults, _ := subKeyring.Lookup(image)
|
||||
authConfigs = append(authConfigs, currAuthResults...)
|
||||
}
|
||||
|
||||
return authConfigs, (len(authConfigs) > 0)
|
||||
}
|
||||
|
||||
// MakeDockerKeyring inspects the passedSecrets to see if they contain any DockerConfig secrets. If they do,
|
||||
// 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 DockerKeyring) (DockerKeyring, error) {
|
||||
passedCredentials := []DockerConfig{}
|
||||
for _, passedSecret := range passedSecrets {
|
||||
if dockerConfigJsonBytes, dockerConfigJsonExists := passedSecret.Data[v1.DockerConfigJsonKey]; (passedSecret.Type == v1.SecretTypeDockerConfigJson) && dockerConfigJsonExists && (len(dockerConfigJsonBytes) > 0) {
|
||||
dockerConfigJson := 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 := DockerConfig{}
|
||||
if err := json.Unmarshal(dockercfgBytes, &dockercfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
passedCredentials = append(passedCredentials, dockercfg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(passedCredentials) > 0 {
|
||||
basicKeyring := &BasicDockerKeyring{}
|
||||
for _, currCredentials := range passedCredentials {
|
||||
basicKeyring.Add(currCredentials)
|
||||
}
|
||||
return &unionDockerKeyring{[]DockerKeyring{basicKeyring, defaultKeyring}}, nil
|
||||
}
|
||||
|
||||
return defaultKeyring, nil
|
||||
}
|
618
vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go
generated
vendored
Normal file
618
vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go
generated
vendored
Normal file
@ -0,0 +1,618 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestUrlsMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
globUrl string
|
||||
targetUrl string
|
||||
matchExpected bool
|
||||
}{
|
||||
// match when there is no path component
|
||||
{
|
||||
globUrl: "*.kubernetes.io",
|
||||
targetUrl: "prefix.kubernetes.io",
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
globUrl: "prefix.*.io",
|
||||
targetUrl: "prefix.kubernetes.io",
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
globUrl: "prefix.kubernetes.*",
|
||||
targetUrl: "prefix.kubernetes.io",
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
globUrl: "*-good.kubernetes.io",
|
||||
targetUrl: "prefix-good.kubernetes.io",
|
||||
matchExpected: true,
|
||||
},
|
||||
// match with path components
|
||||
{
|
||||
globUrl: "*.kubernetes.io/blah",
|
||||
targetUrl: "prefix.kubernetes.io/blah",
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
globUrl: "prefix.*.io/foo",
|
||||
targetUrl: "prefix.kubernetes.io/foo/bar",
|
||||
matchExpected: true,
|
||||
},
|
||||
// match with path components and ports
|
||||
{
|
||||
globUrl: "*.kubernetes.io:1111/blah",
|
||||
targetUrl: "prefix.kubernetes.io:1111/blah",
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
globUrl: "prefix.*.io:1111/foo",
|
||||
targetUrl: "prefix.kubernetes.io:1111/foo/bar",
|
||||
matchExpected: true,
|
||||
},
|
||||
// no match when number of parts mismatch
|
||||
{
|
||||
globUrl: "*.kubernetes.io",
|
||||
targetUrl: "kubernetes.io",
|
||||
matchExpected: false,
|
||||
},
|
||||
{
|
||||
globUrl: "*.*.kubernetes.io",
|
||||
targetUrl: "prefix.kubernetes.io",
|
||||
matchExpected: false,
|
||||
},
|
||||
{
|
||||
globUrl: "*.*.kubernetes.io",
|
||||
targetUrl: "kubernetes.io",
|
||||
matchExpected: false,
|
||||
},
|
||||
// no match when some parts mismatch
|
||||
{
|
||||
globUrl: "kubernetes.io",
|
||||
targetUrl: "kubernetes.com",
|
||||
matchExpected: false,
|
||||
},
|
||||
{
|
||||
globUrl: "k*.io",
|
||||
targetUrl: "quay.io",
|
||||
matchExpected: false,
|
||||
},
|
||||
// no match when ports mismatch
|
||||
{
|
||||
globUrl: "*.kubernetes.io:1234/blah",
|
||||
targetUrl: "prefix.kubernetes.io:1111/blah",
|
||||
matchExpected: false,
|
||||
},
|
||||
{
|
||||
globUrl: "prefix.*.io/foo",
|
||||
targetUrl: "prefix.kubernetes.io:1111/foo/bar",
|
||||
matchExpected: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
matched, _ := urlsMatchStr(test.globUrl, test.targetUrl)
|
||||
if matched != test.matchExpected {
|
||||
t.Errorf("Expected match result of %s and %s to be %t, but was %t",
|
||||
test.globUrl, test.targetUrl, test.matchExpected, matched)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerKeyringForGlob(t *testing.T) {
|
||||
tests := []struct {
|
||||
globUrl string
|
||||
targetUrl string
|
||||
}{
|
||||
{
|
||||
globUrl: "https://hello.kubernetes.io",
|
||||
targetUrl: "hello.kubernetes.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.io",
|
||||
targetUrl: "prefix.docker.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.*.io",
|
||||
targetUrl: "prefix.docker.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.docker.*",
|
||||
targetUrl: "prefix.docker.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.io/path",
|
||||
targetUrl: "prefix.docker.io/path",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.*.io/path",
|
||||
targetUrl: "prefix.docker.io/path/subpath",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.docker.*/path",
|
||||
targetUrl: "prefix.docker.io/path",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.io:8888",
|
||||
targetUrl: "prefix.docker.io:8888",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.*.io:8888",
|
||||
targetUrl: "prefix.docker.io:8888",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.docker.*:8888",
|
||||
targetUrl: "prefix.docker.io:8888",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.io/path:1111",
|
||||
targetUrl: "prefix.docker.io/path:1111",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.io/v1/",
|
||||
targetUrl: "prefix.docker.io/path:1111",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.io/v2/",
|
||||
targetUrl: "prefix.docker.io/path:1111",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.docker.*/path:1111",
|
||||
targetUrl: "prefix.docker.io/path:1111",
|
||||
},
|
||||
{
|
||||
globUrl: "prefix.docker.io:1111",
|
||||
targetUrl: "prefix.docker.io:1111/path",
|
||||
},
|
||||
{
|
||||
globUrl: "*.docker.io:1111",
|
||||
targetUrl: "prefix.docker.io:1111/path",
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, test.globUrl, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
}
|
||||
|
||||
creds, ok := keyring.Lookup(test.targetUrl + "/foo/bar")
|
||||
if !ok {
|
||||
t.Errorf("%d: Didn't find expected URL: %s", i, test.targetUrl)
|
||||
continue
|
||||
}
|
||||
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 TestKeyringMiss(t *testing.T) {
|
||||
tests := []struct {
|
||||
globUrl string
|
||||
lookupUrl string
|
||||
}{
|
||||
{
|
||||
globUrl: "https://hello.kubernetes.io",
|
||||
lookupUrl: "world.mesos.org/foo/bar",
|
||||
},
|
||||
{
|
||||
globUrl: "https://*.docker.com",
|
||||
lookupUrl: "prefix.docker.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://suffix.*.io",
|
||||
lookupUrl: "prefix.docker.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.docker.c*",
|
||||
lookupUrl: "prefix.docker.io",
|
||||
},
|
||||
{
|
||||
globUrl: "https://prefix.*.io/path:1111",
|
||||
lookupUrl: "prefix.docker.io/path/subpath:1111",
|
||||
},
|
||||
{
|
||||
globUrl: "suffix.*.io",
|
||||
lookupUrl: "prefix.docker.io",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, test.globUrl, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
}
|
||||
|
||||
_, ok := keyring.Lookup(test.lookupUrl + "/foo/bar")
|
||||
if ok {
|
||||
t.Errorf("Expected not to find URL %s, but found", test.lookupUrl)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestKeyringMissWithDockerHubCredentials(t *testing.T) {
|
||||
url := defaultRegistryKey
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"https://%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
}
|
||||
|
||||
val, ok := keyring.Lookup("world.mesos.org/foo/bar")
|
||||
if ok {
|
||||
t.Errorf("Found unexpected credential: %+v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyringHitWithUnqualifiedDockerHub(t *testing.T) {
|
||||
url := defaultRegistryKey
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"https://%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
}
|
||||
|
||||
creds, ok := keyring.Lookup("google/docker-registry")
|
||||
if !ok {
|
||||
t.Errorf("Didn't find expected URL: %s", url)
|
||||
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 TestKeyringHitWithUnqualifiedLibraryDockerHub(t *testing.T) {
|
||||
url := defaultRegistryKey
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"https://%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
}
|
||||
|
||||
creds, ok := keyring.Lookup("jenkins")
|
||||
if !ok {
|
||||
t.Errorf("Didn't find expected URL: %s", url)
|
||||
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 TestKeyringHitWithQualifiedDockerHub(t *testing.T) {
|
||||
url := defaultRegistryKey
|
||||
email := "foo@bar.baz"
|
||||
username := "foo"
|
||||
password := "bar"
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
sampleDockerConfig := fmt.Sprintf(`{
|
||||
"https://%s": {
|
||||
"email": %q,
|
||||
"auth": %q
|
||||
}
|
||||
}`, url, email, auth)
|
||||
|
||||
keyring := &BasicDockerKeyring{}
|
||||
if cfg, err := readDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
|
||||
t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
|
||||
} else {
|
||||
keyring.Add(cfg)
|
||||
}
|
||||
|
||||
creds, ok := keyring.Lookup(url + "/google/docker-registry")
|
||||
if !ok {
|
||||
t.Errorf("Didn't find expected URL: %s", url)
|
||||
return
|
||||
}
|
||||
if len(creds) > 2 {
|
||||
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 TestIsDefaultRegistryMatch(t *testing.T) {
|
||||
samples := []map[bool]string{
|
||||
{true: "foo/bar"},
|
||||
{true: "docker.io/foo/bar"},
|
||||
{true: "index.docker.io/foo/bar"},
|
||||
{true: "foo"},
|
||||
{false: ""},
|
||||
{false: "registry.tld/foo/bar"},
|
||||
{false: "registry:5000/foo/bar"},
|
||||
{false: "myhostdocker.io/foo/bar"},
|
||||
}
|
||||
for _, sample := range samples {
|
||||
for expected, imageName := range sample {
|
||||
if got := isDefaultRegistryMatch(imageName); got != expected {
|
||||
t.Errorf("Expected '%s' to be %t, got %t", imageName, expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testProvider struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// Enabled implements dockerConfigProvider
|
||||
func (d *testProvider) Enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyProvide implements dockerConfigProvider. Should never be called.
|
||||
func (d *testProvider) LazyProvide() *DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements dockerConfigProvider
|
||||
func (d *testProvider) Provide() DockerConfig {
|
||||
d.Count += 1
|
||||
return DockerConfig{}
|
||||
}
|
||||
|
||||
func TestLazyKeyring(t *testing.T) {
|
||||
provider := &testProvider{
|
||||
Count: 0,
|
||||
}
|
||||
lazy := &lazyDockerKeyring{
|
||||
Providers: []DockerConfigProvider{
|
||||
provider,
|
||||
},
|
||||
}
|
||||
|
||||
if provider.Count != 0 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
lazy.Lookup("foo")
|
||||
if provider.Count != 1 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
lazy.Lookup("foo")
|
||||
if provider.Count != 2 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
lazy.Lookup("foo")
|
||||
if provider.Count != 3 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerKeyringLookup(t *testing.T) {
|
||||
ada := LazyAuthConfiguration{
|
||||
AuthConfig: dockertypes.AuthConfig{
|
||||
Username: "ada",
|
||||
Password: "smash",
|
||||
Email: "ada@example.com",
|
||||
},
|
||||
}
|
||||
|
||||
grace := LazyAuthConfiguration{
|
||||
AuthConfig: dockertypes.AuthConfig{
|
||||
Username: "grace",
|
||||
Password: "squash",
|
||||
Email: "grace@example.com",
|
||||
},
|
||||
}
|
||||
|
||||
dk := &BasicDockerKeyring{}
|
||||
dk.Add(DockerConfig{
|
||||
"bar.example.com/pong": DockerConfigEntry{
|
||||
Username: grace.Username,
|
||||
Password: grace.Password,
|
||||
Email: grace.Email,
|
||||
},
|
||||
"bar.example.com": DockerConfigEntry{
|
||||
Username: ada.Username,
|
||||
Password: ada.Password,
|
||||
Email: ada.Email,
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
image string
|
||||
match []LazyAuthConfiguration
|
||||
ok bool
|
||||
}{
|
||||
// direct match
|
||||
{"bar.example.com", []LazyAuthConfiguration{ada}, true},
|
||||
|
||||
// direct match deeper than other possible matches
|
||||
{"bar.example.com/pong", []LazyAuthConfiguration{grace, ada}, true},
|
||||
|
||||
// no direct match, deeper path ignored
|
||||
{"bar.example.com/ping", []LazyAuthConfiguration{ada}, true},
|
||||
|
||||
// match first part of path token
|
||||
{"bar.example.com/pongz", []LazyAuthConfiguration{grace, ada}, true},
|
||||
|
||||
// match regardless of sub-path
|
||||
{"bar.example.com/pong/pang", []LazyAuthConfiguration{grace, ada}, true},
|
||||
|
||||
// no host match
|
||||
{"example.com", []LazyAuthConfiguration{}, false},
|
||||
{"foo.example.com", []LazyAuthConfiguration{}, false},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
match, ok := dk.Lookup(tt.image)
|
||||
if tt.ok != ok {
|
||||
t.Errorf("case %d: expected ok=%t, got %t", i, tt.ok, ok)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.match, match) {
|
||||
t.Errorf("case %d: expected match=%#v, got %#v", i, tt.match, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This validates that dockercfg entries with a scheme and url path are properly matched
|
||||
// by images that only match the hostname.
|
||||
// NOTE: the above covers the case of a more specific match trumping just hostname.
|
||||
func TestIssue3797(t *testing.T) {
|
||||
rex := LazyAuthConfiguration{
|
||||
AuthConfig: dockertypes.AuthConfig{
|
||||
Username: "rex",
|
||||
Password: "tiny arms",
|
||||
Email: "rex@example.com",
|
||||
},
|
||||
}
|
||||
|
||||
dk := &BasicDockerKeyring{}
|
||||
dk.Add(DockerConfig{
|
||||
"https://quay.io/v1/": DockerConfigEntry{
|
||||
Username: rex.Username,
|
||||
Password: rex.Password,
|
||||
Email: rex.Email,
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
image string
|
||||
match []LazyAuthConfiguration
|
||||
ok bool
|
||||
}{
|
||||
// direct match
|
||||
{"quay.io", []LazyAuthConfiguration{rex}, true},
|
||||
|
||||
// partial matches
|
||||
{"quay.io/foo", []LazyAuthConfiguration{rex}, true},
|
||||
{"quay.io/foo/bar", []LazyAuthConfiguration{rex}, true},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
match, ok := dk.Lookup(tt.image)
|
||||
if tt.ok != ok {
|
||||
t.Errorf("case %d: expected ok=%t, got %t", i, tt.ok, ok)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.match, match) {
|
||||
t.Errorf("case %d: expected match=%#v, got %#v", i, tt.match, match)
|
||||
}
|
||||
}
|
||||
}
|
62
vendor/k8s.io/kubernetes/pkg/credentialprovider/plugins.go
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/credentialprovider/plugins.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 (
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// All registered credential providers.
|
||||
var providersMutex sync.Mutex
|
||||
var providers = make(map[string]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) {
|
||||
providersMutex.Lock()
|
||||
defer providersMutex.Unlock()
|
||||
_, found := providers[name]
|
||||
if found {
|
||||
glog.Fatalf("Credential provider %q was registered twice", name)
|
||||
}
|
||||
glog.V(4).Infof("Registered credential provider %q", name)
|
||||
providers[name] = provider
|
||||
}
|
||||
|
||||
// NewDockerKeyring creates a DockerKeyring to use for resolving credentials,
|
||||
// which lazily draws from the set of registered credential providers.
|
||||
func NewDockerKeyring() DockerKeyring {
|
||||
keyring := &lazyDockerKeyring{
|
||||
Providers: make([]DockerConfigProvider, 0),
|
||||
}
|
||||
|
||||
// TODO(mattmoor): iterating over the map is non-deterministic. We should
|
||||
// introduce the notion of priorities for conflict resolution.
|
||||
for name, provider := range providers {
|
||||
if provider.Enabled() {
|
||||
glog.V(4).Infof("Registering credential provider: %v", name)
|
||||
keyring.Providers = append(keyring.Providers, provider)
|
||||
}
|
||||
}
|
||||
|
||||
return keyring
|
||||
}
|
123
vendor/k8s.io/kubernetes/pkg/credentialprovider/provider.go
generated
vendored
Normal file
123
vendor/k8s.io/kubernetes/pkg/credentialprovider/provider.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// 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.
|
||||
Provide() DockerConfig
|
||||
// LazyProvide() gets called after URL matches have been performed, so the
|
||||
// location used as the key in DockerConfig would be redundant.
|
||||
LazyProvide() *DockerConfigEntry
|
||||
}
|
||||
|
||||
func LazyProvide(creds LazyAuthConfiguration) dockertypes.AuthConfig {
|
||||
if creds.Provider != nil {
|
||||
entry := *creds.Provider.LazyProvide()
|
||||
return DockerConfigEntryToLazyAuthConfiguration(entry).AuthConfig
|
||||
} else {
|
||||
return creds.AuthConfig
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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() DockerConfig {
|
||||
// Read the standard Docker credentials from .dockercfg
|
||||
if cfg, err := ReadDockerConfigFile(); err == nil {
|
||||
return cfg
|
||||
} else if !os.IsNotExist(err) {
|
||||
glog.V(4).Infof("Unable to parse Docker config file: %v", err)
|
||||
}
|
||||
return DockerConfig{}
|
||||
}
|
||||
|
||||
// LazyProvide implements dockerConfigProvider. Should never be called.
|
||||
func (d *defaultDockerConfigProvider) LazyProvide() *DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enabled implements dockerConfigProvider
|
||||
func (d *CachingDockerConfigProvider) Enabled() bool {
|
||||
return d.Provider.Enabled()
|
||||
}
|
||||
|
||||
// LazyProvide implements dockerConfigProvider. Should never be called.
|
||||
func (d *CachingDockerConfigProvider) LazyProvide() *DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements dockerConfigProvider
|
||||
func (d *CachingDockerConfigProvider) Provide() 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
|
||||
}
|
||||
|
||||
glog.V(2).Infof("Refreshing cache for provider: %v", reflect.TypeOf(d.Provider).String())
|
||||
d.cacheDockerConfig = d.Provider.Provide()
|
||||
d.expiration = time.Now().Add(d.Lifetime)
|
||||
return d.cacheDockerConfig
|
||||
}
|
62
vendor/k8s.io/kubernetes/pkg/credentialprovider/provider_test.go
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/credentialprovider/provider_test.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
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,
|
||||
}
|
||||
|
||||
if provider.Count != 0 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
if provider.Count != 1 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
|
||||
time.Sleep(cache.Lifetime)
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
if provider.Count != 2 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
|
||||
time.Sleep(cache.Lifetime)
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
cache.Provide()
|
||||
if provider.Count != 3 {
|
||||
t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
|
||||
}
|
||||
}
|
45
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/BUILD
generated
vendored
Normal file
45
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/BUILD
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["rancher_registry_credentials_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/rancher",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/rancher/go-rancher/client:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"rancher_registry_credentials.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/credentialprovider/rancher",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/rancher/go-rancher/client:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
17
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/doc.go
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/doc.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2017 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 rancher_credentials
|
145
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/rancher_registry_credentials.go
generated
vendored
Normal file
145
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/rancher_registry_credentials.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
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 rancher_credentials
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/rancher/go-rancher/client"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
// rancher provider
|
||||
type rancherProvider struct {
|
||||
credGetter credentialsGetter
|
||||
}
|
||||
|
||||
// credentials getter from Rancher private registry
|
||||
type rancherCredentialsGetter struct {
|
||||
client *client.RancherClient
|
||||
}
|
||||
|
||||
type rConfig struct {
|
||||
Global configGlobal
|
||||
}
|
||||
|
||||
// An interface for testing purposes.
|
||||
type credentialsGetter interface {
|
||||
getCredentials() []registryCredential
|
||||
}
|
||||
|
||||
type configGlobal struct {
|
||||
CattleURL string `gcfg:"cattle-url"`
|
||||
CattleAccessKey string `gcfg:"cattle-access-key"`
|
||||
CattleSecretKey string `gcfg:"cattle-secret-key"`
|
||||
}
|
||||
|
||||
type registryCredential struct {
|
||||
credential *client.RegistryCredential
|
||||
serverIP string
|
||||
}
|
||||
|
||||
var rancherGetter = &rancherCredentialsGetter{}
|
||||
|
||||
func init() {
|
||||
credentialprovider.RegisterCredentialProvider("rancher-registry-creds",
|
||||
&credentialprovider.CachingDockerConfigProvider{
|
||||
Provider: &rancherProvider{rancherGetter},
|
||||
Lifetime: 30 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *rancherProvider) Enabled() bool {
|
||||
client, err := getRancherClient()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if client == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rancherGetter.client = client
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyProvide implements DockerConfigProvider. Should never be called.
|
||||
func (p *rancherProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide implements DockerConfigProvider.Provide, refreshing Rancher tokens on demand
|
||||
func (p *rancherProvider) Provide() credentialprovider.DockerConfig {
|
||||
cfg := credentialprovider.DockerConfig{}
|
||||
for _, cred := range p.credGetter.getCredentials() {
|
||||
entry := credentialprovider.DockerConfigEntry{
|
||||
Username: cred.credential.PublicValue,
|
||||
Password: cred.credential.SecretValue,
|
||||
Email: cred.credential.Email,
|
||||
}
|
||||
cfg[cred.serverIP] = entry
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (g *rancherCredentialsGetter) getCredentials() []registryCredential {
|
||||
var registryCreds []registryCredential
|
||||
credColl, err := g.client.RegistryCredential.List(client.NewListOpts())
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to pull registry credentials from rancher %v", err)
|
||||
return registryCreds
|
||||
}
|
||||
for _, cred := range credColl.Data {
|
||||
registry := &client.Registry{}
|
||||
if err = g.client.GetLink(cred.Resource, "registry", registry); err != nil {
|
||||
glog.Errorf("Failed to pull registry from rancher %v", err)
|
||||
return registryCreds
|
||||
}
|
||||
registryCred := registryCredential{
|
||||
credential: &cred,
|
||||
serverIP: registry.ServerAddress,
|
||||
}
|
||||
registryCreds = append(registryCreds, registryCred)
|
||||
}
|
||||
return registryCreds
|
||||
}
|
||||
|
||||
func getRancherClient() (*client.RancherClient, error) {
|
||||
url := os.Getenv("CATTLE_URL")
|
||||
accessKey := os.Getenv("CATTLE_ACCESS_KEY")
|
||||
secretKey := os.Getenv("CATTLE_SECRET_KEY")
|
||||
|
||||
if url == "" || accessKey == "" || secretKey == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
conf := rConfig{
|
||||
Global: configGlobal{
|
||||
CattleURL: url,
|
||||
CattleAccessKey: accessKey,
|
||||
CattleSecretKey: secretKey,
|
||||
},
|
||||
}
|
||||
|
||||
return client.NewRancherClient(&client.ClientOpts{
|
||||
Url: conf.Global.CattleURL,
|
||||
AccessKey: conf.Global.CattleAccessKey,
|
||||
SecretKey: conf.Global.CattleSecretKey,
|
||||
})
|
||||
}
|
120
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/rancher_registry_credentials_test.go
generated
vendored
Normal file
120
vendor/k8s.io/kubernetes/pkg/credentialprovider/rancher/rancher_registry_credentials_test.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 rancher_credentials
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/rancher/go-rancher/client"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
)
|
||||
|
||||
const username = "foo"
|
||||
const password = "qwerty"
|
||||
const email = "foo@bar.baz"
|
||||
|
||||
var serverAddresses = []string{"quay.io", "192.168.5.0"}
|
||||
|
||||
type testCredentialsGetter struct {
|
||||
client *client.RancherClient
|
||||
}
|
||||
|
||||
func (p *testCredentialsGetter) getCredentials() []registryCredential {
|
||||
var registryCreds []registryCredential
|
||||
|
||||
for _, serverAddress := range serverAddresses {
|
||||
cred := &client.RegistryCredential{
|
||||
PublicValue: username,
|
||||
SecretValue: password,
|
||||
Email: email,
|
||||
}
|
||||
registryCred := registryCredential{
|
||||
credential: cred,
|
||||
serverIP: serverAddress,
|
||||
}
|
||||
registryCreds = append(registryCreds, registryCred)
|
||||
}
|
||||
|
||||
return registryCreds
|
||||
}
|
||||
|
||||
func TestRancherCredentialsProvide(t *testing.T) {
|
||||
image := "foo/bar"
|
||||
|
||||
url := "http://localhost:8080"
|
||||
accessKey := "B481F55E0C48C546E094"
|
||||
secretKey := "dND2fBcytWWvCRJ8LvqnYcjyNfEkaikvfVxk2C5r"
|
||||
conf := rConfig{
|
||||
Global: configGlobal{
|
||||
CattleURL: url,
|
||||
CattleAccessKey: accessKey,
|
||||
CattleSecretKey: secretKey,
|
||||
},
|
||||
}
|
||||
|
||||
rancherClient, _ := client.NewRancherClient(&client.ClientOpts{
|
||||
Url: conf.Global.CattleURL,
|
||||
AccessKey: conf.Global.CattleAccessKey,
|
||||
SecretKey: conf.Global.CattleSecretKey,
|
||||
})
|
||||
|
||||
testGetter := &testCredentialsGetter{
|
||||
client: rancherClient,
|
||||
}
|
||||
|
||||
provider := &rancherProvider{
|
||||
credGetter: testGetter,
|
||||
}
|
||||
|
||||
keyring := &credentialprovider.BasicDockerKeyring{}
|
||||
keyring.Add(provider.Provide())
|
||||
|
||||
for _, registry := range serverAddresses {
|
||||
fullImagePath := path.Join(registry, image)
|
||||
creds, ok := keyring.Lookup(fullImagePath)
|
||||
if !ok {
|
||||
t.Errorf("Didn't find expected image: %s", fullImagePath)
|
||||
return
|
||||
}
|
||||
|
||||
if len(creds) > 1 {
|
||||
t.Errorf("Expected 1 result, received %v", len(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)
|
||||
}
|
||||
}
|
||||
|
||||
// try to fetch non-existing registry
|
||||
fullImagePath := path.Join("1.1.1.1", image)
|
||||
_, ok := keyring.Lookup(fullImagePath)
|
||||
if ok {
|
||||
t.Errorf("Found non-existing image: %s", fullImagePath)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user