Merge pull request #94196 from andrewsykim/registry-creds

kubelet: add alpha credential provider plugins
This commit is contained in:
Kubernetes Prow Robot 2020-11-11 19:59:11 -08:00 committed by GitHub
commit d233111f5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 3277 additions and 18 deletions

View File

@ -385,6 +385,10 @@ API rule violation: list_type_missing,k8s.io/kube-scheduler/config/v1,Policy,Pri
API rule violation: list_type_missing,k8s.io/kube-scheduler/config/v1,RequestedToCapacityRatioArguments,Resources
API rule violation: list_type_missing,k8s.io/kube-scheduler/config/v1,RequestedToCapacityRatioArguments,Shape
API rule violation: list_type_missing,k8s.io/kube-scheduler/config/v1,ServiceAffinity,Labels
API rule violation: list_type_missing,k8s.io/kubelet/config/v1alpha1,CredentialProvider,Args
API rule violation: list_type_missing,k8s.io/kubelet/config/v1alpha1,CredentialProvider,Env
API rule violation: list_type_missing,k8s.io/kubelet/config/v1alpha1,CredentialProvider,MatchImages
API rule violation: list_type_missing,k8s.io/kubelet/config/v1alpha1,CredentialProviderConfig,Providers
API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,AllowedUnsafeSysctls
API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,ClusterDNS
API rule violation: list_type_missing,k8s.io/kubelet/config/v1beta1,KubeletConfiguration,EnforceNodeAllocatable

View File

@ -101,6 +101,7 @@ tags_values_pkgs = {"openapi-gen": {
"staging/src/k8s.io/kube-proxy/config/v1alpha1",
"staging/src/k8s.io/kube-scheduler/config/v1",
"staging/src/k8s.io/kube-scheduler/config/v1beta1",
"staging/src/k8s.io/kubelet/config/v1alpha1",
"staging/src/k8s.io/kubelet/config/v1beta1",
"staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1beta1",
"staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1beta2",
@ -188,6 +189,7 @@ tags_pkgs_values = {"openapi-gen": {
"staging/src/k8s.io/kube-proxy/config/v1alpha1": ["true"],
"staging/src/k8s.io/kube-scheduler/config/v1": ["true"],
"staging/src/k8s.io/kube-scheduler/config/v1beta1": ["true"],
"staging/src/k8s.io/kubelet/config/v1alpha1": ["true"],
"staging/src/k8s.io/kubelet/config/v1beta1": ["true"],
"staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1beta1": ["true"],
"staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1beta2": ["true"],

View File

@ -1130,6 +1130,8 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
kubeServer.CloudProvider,
kubeServer.CertDirectory,
kubeServer.RootDirectory,
kubeServer.ImageCredentialProviderConfigFile,
kubeServer.ImageCredentialProviderBinDir,
kubeServer.RegisterNode,
kubeServer.RegisterWithTaints,
kubeServer.AllowedUnsafeSysctls,
@ -1204,6 +1206,8 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
cloudProvider string,
certDirectory string,
rootDirectory string,
imageCredentialProviderConfigFile string,
imageCredentialProviderBinDir string,
registerNode bool,
registerWithTaints []api.Taint,
allowedUnsafeSysctls []string,
@ -1235,6 +1239,8 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
cloudProvider,
certDirectory,
rootDirectory,
imageCredentialProviderConfigFile,
imageCredentialProviderBinDir,
registerNode,
registerWithTaints,
allowedUnsafeSysctls,

View File

@ -88,6 +88,7 @@ pkg/controller/volume/persistentvolume
pkg/controller/volume/persistentvolume/config/v1alpha1
pkg/controlplane/controller/crdregistration
pkg/controlplane/tunneler
pkg/credentialprovider/plugin
pkg/features
pkg/kubeapiserver
pkg/kubectl/cmd/convert
@ -455,6 +456,8 @@ staging/src/k8s.io/kubectl/pkg/polymorphichelpers
staging/src/k8s.io/kubectl/pkg/scale
staging/src/k8s.io/kubectl/pkg/util/templates
staging/src/k8s.io/kubelet/config/v1beta1
staging/src/k8s.io/kubelet/pkg/apis/credentialprovider
staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1
staging/src/k8s.io/legacy-cloud-providers/vsphere
staging/src/k8s.io/metrics/pkg/apis/custom_metrics
staging/src/k8s.io/metrics/pkg/apis/custom_metrics/v1beta1

View File

@ -46,6 +46,7 @@ filegroup(
"//pkg/credentialprovider/aws:all-srcs",
"//pkg/credentialprovider/azure:all-srcs",
"//pkg/credentialprovider/gcp:all-srcs",
"//pkg/credentialprovider/plugin:all-srcs",
"//pkg/credentialprovider/secrets:all-srcs",
],
tags = ["automanaged"],

View File

@ -158,9 +158,10 @@ func isDefaultRegistryMatch(image string) bool {
return !strings.ContainsAny(parts[0], ".:")
}
// ParseSchemelessURL parses a schemeless url and returns a url.URL
// 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) {
func ParseSchemelessURL(schemelessURL string) (*url.URL, error) {
parsed, err := url.Parse("https://" + schemelessURL)
if err != nil {
return nil, err
@ -170,8 +171,8 @@ func parseSchemelessURL(schemelessURL string) (*url.URL, error) {
return parsed, nil
}
// split the host name into parts, as well as the port
func splitURL(url *url.URL) (parts []string, port string) {
// SplitURL splits 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
@ -180,20 +181,20 @@ func splitURL(url *url.URL) (parts []string, port string) {
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)
// URLsMatchStr is wrapper for 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)
targetURL, err := ParseSchemelessURL(target)
if err != nil {
return false, err
}
return urlsMatch(globURL, targetURL)
return URLsMatch(globURL, targetURL)
}
// check whether the given target url matches the glob url, which may have
// URLsMatch checks whether the given target url matches the glob url, which may have
// glob wild cards in the host name.
//
// Examples:
@ -201,9 +202,9 @@ func urlsMatchStr(glob string, target string) (bool, error) {
// 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)
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
@ -240,7 +241,7 @@ func (dk *BasicDockerKeyring) Lookup(image string) ([]AuthConfig, bool) {
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 {
if matched, _ := URLsMatchStr(k, image); matched {
ret = append(ret, dk.creds[k]...)
}
}

View File

@ -23,7 +23,7 @@ import (
"testing"
)
func TestUrlsMatch(t *testing.T) {
func TestURLsMatch(t *testing.T) {
tests := []struct {
globURL string
targetURL string
@ -112,7 +112,7 @@ func TestUrlsMatch(t *testing.T) {
},
}
for _, test := range tests {
matched, _ := urlsMatchStr(test.globURL, test.targetURL)
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)

View File

@ -0,0 +1,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"config.go",
"plugin.go",
],
importpath = "k8s.io/kubernetes/pkg/credentialprovider/plugin",
visibility = ["//visibility:public"],
deps = [
"//pkg/credentialprovider:go_default_library",
"//pkg/kubelet/apis/config:go_default_library",
"//pkg/kubelet/apis/config/v1alpha1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"config_test.go",
"plugin_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/credentialprovider:go_default_library",
"//pkg/kubelet/apis/config:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1:go_default_library",
],
)

View File

@ -0,0 +1,128 @@
/*
Copyright 2020 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 plugin
import (
"fmt"
"io/ioutil"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/credentialprovider"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
)
// readCredentialProviderConfigFile receives a path to a config file and decodes it
// into the internal CredentialProviderConfig type.
func readCredentialProviderConfigFile(configPath string) (*kubeletconfig.CredentialProviderConfig, error) {
if configPath == "" {
return nil, fmt.Errorf("credential provider config path is empty")
}
data, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("unable to read external registry credential provider configuration from %q: %v", configPath, err)
}
config, err := decode(data)
if err != nil {
return nil, fmt.Errorf("error decoding config %s: %v", configPath, err)
}
return config, nil
}
// decode decodes data into the internal CredentialProviderConfig type.
func decode(data []byte) (*kubeletconfig.CredentialProviderConfig, error) {
obj, gvk, err := codecs.UniversalDecoder().Decode(data, nil, nil)
if err != nil {
return nil, err
}
if gvk.Kind != "CredentialProviderConfig" {
return nil, fmt.Errorf("failed to decode %q (wrong Kind)", gvk.Kind)
}
if gvk.Group != kubeletconfig.GroupName {
return nil, fmt.Errorf("failed to decode CredentialProviderConfig, unexpected Group: %s", gvk.Group)
}
if internalConfig, ok := obj.(*kubeletconfig.CredentialProviderConfig); ok {
return internalConfig, nil
}
return nil, fmt.Errorf("unable to convert %T to *CredentialProviderConfig", obj)
}
// validateCredentialProviderConfig validates CredentialProviderConfig.
func validateCredentialProviderConfig(config *kubeletconfig.CredentialProviderConfig) field.ErrorList {
allErrs := field.ErrorList{}
if len(config.Providers) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("providers"), "at least 1 item in plugins is required"))
}
fieldPath := field.NewPath("providers")
for _, provider := range config.Providers {
if strings.Contains(provider.Name, "/") {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot contain '/'"))
}
if strings.Contains(provider.Name, " ") {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot contain spaces"))
}
if provider.Name == "." {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot be '.'"))
}
if provider.Name == ".." {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot be '..'"))
}
if provider.APIVersion == "" {
allErrs = append(allErrs, field.Required(fieldPath.Child("apiVersion"), "apiVersion is required"))
} else if _, ok := apiVersions[provider.APIVersion]; !ok {
validAPIVersions := []string{}
for apiVersion := range apiVersions {
validAPIVersions = append(validAPIVersions, apiVersion)
}
allErrs = append(allErrs, field.NotSupported(fieldPath.Child("apiVersion"), provider.APIVersion, validAPIVersions))
}
if len(provider.MatchImages) == 0 {
allErrs = append(allErrs, field.Required(fieldPath.Child("matchImages"), "at least 1 item in matchImages is required"))
}
for _, matchImage := range provider.MatchImages {
if _, err := credentialprovider.ParseSchemelessURL(matchImage); err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("matchImages"), matchImage, fmt.Sprintf("match image is invalid: %s", err.Error())))
}
}
if provider.DefaultCacheDuration == nil {
allErrs = append(allErrs, field.Required(fieldPath.Child("defaultCacheDuration"), "defaultCacheDuration is required"))
}
if provider.DefaultCacheDuration != nil && provider.DefaultCacheDuration.Duration < 0 {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("defaultCacheDuration"), provider.DefaultCacheDuration.Duration, "defaultCacheDuration must be greater than or equal to 0"))
}
}
return allErrs
}

View File

@ -0,0 +1,435 @@
/*
Copyright 2020 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 plugin
import (
"io/ioutil"
"os"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
)
func Test_readCredentialProviderConfigFile(t *testing.T) {
testcases := []struct {
name string
configData string
config *kubeletconfig.CredentialProviderConfig
expectErr bool
}{
{
name: "config with 1 plugin and 1 image matcher",
configData: `---
kind: CredentialProviderConfig
apiVersion: kubelet.config.k8s.io/v1alpha1
providers:
- name: test
matchImages:
- "registry.io/foobar"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
args:
- --v=5
env:
- name: FOO
value: BAR`,
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "test",
MatchImages: []string{"registry.io/foobar"},
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
Args: []string{"--v=5"},
Env: []kubeletconfig.ExecEnvVar{
{
Name: "FOO",
Value: "BAR",
},
},
},
},
},
},
{
name: "config with 1 plugin and a wildcard image match",
configData: `---
kind: CredentialProviderConfig
apiVersion: kubelet.config.k8s.io/v1alpha1
providers:
- name: test
matchImages:
- "registry.io/*"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
args:
- --v=5
env:
- name: FOO
value: BAR`,
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "test",
MatchImages: []string{"registry.io/*"},
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
Args: []string{"--v=5"},
Env: []kubeletconfig.ExecEnvVar{
{
Name: "FOO",
Value: "BAR",
},
},
},
},
},
},
{
name: "config with 1 plugin and multiple image matchers",
configData: `---
kind: CredentialProviderConfig
apiVersion: kubelet.config.k8s.io/v1alpha1
providers:
- name: test
matchImages:
- "registry.io/*"
- "foobar.registry.io/*"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
args:
- --v=5
env:
- name: FOO
value: BAR`,
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "test",
MatchImages: []string{"registry.io/*", "foobar.registry.io/*"},
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
Args: []string{"--v=5"},
Env: []kubeletconfig.ExecEnvVar{
{
Name: "FOO",
Value: "BAR",
},
},
},
},
},
},
{
name: "config with multiple providers",
configData: `---
kind: CredentialProviderConfig
apiVersion: kubelet.config.k8s.io/v1alpha1
providers:
- name: test1
matchImages:
- "registry.io/one"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
- name: test2
matchImages:
- "registry.io/two"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
args:
- --v=5
env:
- name: FOO
value: BAR`,
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "test1",
MatchImages: []string{"registry.io/one"},
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
{
Name: "test2",
MatchImages: []string{"registry.io/two"},
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
Args: []string{"--v=5"},
Env: []kubeletconfig.ExecEnvVar{
{
Name: "FOO",
Value: "BAR",
},
},
},
},
},
},
{
name: "config with wrong Kind",
configData: `---
kind: WrongKind
apiVersion: kubelet.config.k8s.io/v1alpha1
providers:
- name: test
matchImages:
- "registry.io/foobar"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
args:
- --v=5
env:
- name: FOO
value: BAR`,
config: nil,
expectErr: true,
},
{
name: "config with wrong apiversion",
configData: `---
kind: CredentialProviderConfig
apiVersion: foobar/v1alpha1
providers:
- name: test
matchImages:
- "registry.io/foobar"
defaultCacheDuration: 10m
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
args:
- --v=5
env:
- name: FOO
value: BAR`,
config: nil,
expectErr: true,
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
file, err := ioutil.TempFile("", "config.yaml")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
_, err = file.WriteString(testcase.configData)
if err != nil {
t.Fatal(err)
}
authConfig, err := readCredentialProviderConfigFile(file.Name())
if err != nil && !testcase.expectErr {
t.Fatal(err)
}
if err == nil && testcase.expectErr {
t.Error("expected error but got none")
}
if !reflect.DeepEqual(authConfig, testcase.config) {
t.Logf("actual auth config: %#v", authConfig)
t.Logf("expected auth config: %#v", testcase.config)
t.Error("credential provider config did not match")
}
})
}
}
func Test_validateCredentialProviderConfig(t *testing.T) {
testcases := []struct {
name string
config *kubeletconfig.CredentialProviderConfig
shouldErr bool
}{
{
name: "no providers provided",
config: &kubeletconfig.CredentialProviderConfig{},
shouldErr: true,
},
{
name: "no matchImages provided",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "no default cache duration provided",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{"foobar.registry.io"},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "name contains '/'",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foo/../bar",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "name is '.'",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: ".",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "name is '..'",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "..",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "name contains spaces",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foo bar",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "no apiVersion",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "",
},
},
},
shouldErr: true,
},
{
name: "invalid apiVersion",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha0",
},
},
},
shouldErr: true,
},
{
name: "negative default cache duration",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: -1 * time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "invalid match image",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{"%invalid%"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: true,
},
{
name: "valid config",
config: &kubeletconfig.CredentialProviderConfig{
Providers: []kubeletconfig.CredentialProvider{
{
Name: "foobar",
MatchImages: []string{"foobar.registry.io"},
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
},
},
},
shouldErr: false,
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
errs := validateCredentialProviderConfig(testcase.config)
if testcase.shouldErr && len(errs) == 0 {
t.Errorf("expected error but got none")
} else if !testcase.shouldErr && len(errs) > 0 {
t.Errorf("expected no error but received errors: %v", errs.ToAggregate())
}
})
}
}

View File

@ -0,0 +1,420 @@
/*
Copyright 2020 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 plugin
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
credentialproviderapi "k8s.io/kubelet/pkg/apis/credentialprovider"
"k8s.io/kubelet/pkg/apis/credentialprovider/install"
credentialproviderv1alpha1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1"
"k8s.io/kubernetes/pkg/credentialprovider"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/config/v1alpha1"
)
const (
globalCacheKey = "global"
)
var (
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
apiVersions = map[string]schema.GroupVersion{
credentialproviderv1alpha1.SchemeGroupVersion.String(): credentialproviderv1alpha1.SchemeGroupVersion,
}
)
func init() {
install.Install(scheme)
kubeletconfig.AddToScheme(scheme)
kubeletconfigv1alpha1.AddToScheme(scheme)
}
// RegisterCredentialProviderPlugins is called from kubelet to register external credential provider
// plugins according to the CredentialProviderConfig config file.
func RegisterCredentialProviderPlugins(pluginConfigFile, pluginBinDir string) error {
if _, err := os.Stat(pluginBinDir); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("plugin binary directory %s did not exist", pluginBinDir)
}
return fmt.Errorf("error inspecting binary directory %s: %w", pluginBinDir, err)
}
credentialProviderConfig, err := readCredentialProviderConfigFile(pluginConfigFile)
if err != nil {
return err
}
errs := validateCredentialProviderConfig(credentialProviderConfig)
if len(errs) > 0 {
return fmt.Errorf("failed to validate credential provider config: %v", errs.ToAggregate())
}
for _, provider := range credentialProviderConfig.Providers {
pluginBin := filepath.Join(pluginBinDir, provider.Name)
if _, err := os.Stat(pluginBin); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("plugin binary executable %s did not exist", pluginBin)
}
return fmt.Errorf("error inspecting binary executable %s: %w", pluginBin, err)
}
plugin, err := newPluginProvider(pluginBinDir, provider)
if err != nil {
return fmt.Errorf("error initializing plugin provider %s: %w", provider.Name, err)
}
credentialprovider.RegisterCredentialProvider(provider.Name, plugin)
}
return nil
}
// newPluginProvider returns a new pluginProvider based on the credential provider config.
func newPluginProvider(pluginBinDir string, provider kubeletconfig.CredentialProvider) (*pluginProvider, error) {
mediaType := "application/json"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return nil, fmt.Errorf("unsupported media type %q", mediaType)
}
gv, ok := apiVersions[provider.APIVersion]
if !ok {
return nil, fmt.Errorf("invalid apiVersion: %q", provider.APIVersion)
}
return &pluginProvider{
matchImages: provider.MatchImages,
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
defaultCacheDuration: provider.DefaultCacheDuration.Duration,
plugin: &execPlugin{
name: provider.Name,
apiVersion: provider.APIVersion,
encoder: codecs.EncoderForVersion(info.Serializer, gv),
pluginBinDir: pluginBinDir,
args: provider.Args,
envVars: provider.Env,
},
}, nil
}
// pluginProvider is the plugin-based implementation of the DockerConfigProvider interface.
type pluginProvider struct {
sync.Mutex
// matchImages defines the matching image URLs this plugin should operate against.
// The plugin provider will not return any credentials for images that do not match
// against this list of match URLs.
matchImages []string
// cache stores DockerConfig entries with an expiration time based on the cache duration
// returned from the credential provider plugin.
cache cache.Store
// defaultCacheDuration is the default duration credentials are cached in-memory if the auth plugin
// response did not provide a cache duration for credentials.
defaultCacheDuration time.Duration
// plugin is the exec implementation of the credential providing plugin.
plugin Plugin
}
// cacheEntry is the cache object that will be stored in cache.Store.
type cacheEntry struct {
key string
credentials credentialprovider.DockerConfig
expiresAt time.Time
}
// cacheKeyFunc extracts AuthEntry.MatchKey as the cache key function for the plugin provider.
func cacheKeyFunc(obj interface{}) (string, error) {
key := obj.(*cacheEntry).key
return key, nil
}
// cacheExpirationPolicy defines implements cache.ExpirationPolicy, determining expiration based on the expiresAt timestamp.
type cacheExpirationPolicy struct{}
// IsExpired returns true if the current time is after cacheEntry.expiresAt, which is determined by the
// cache duration returned from the credential provider plugin response.
func (c *cacheExpirationPolicy) IsExpired(entry *cache.TimestampedEntry) bool {
return time.Now().After(entry.Obj.(*cacheEntry).expiresAt)
}
// Provide returns a credentialprovider.DockerConfig based on the credentials returned
// from cache or the exec plugin.
func (p *pluginProvider) Provide(image string) credentialprovider.DockerConfig {
if !p.isImageAllowed(image) {
return credentialprovider.DockerConfig{}
}
p.Lock()
defer p.Unlock()
cachedConfig, found, err := p.getCachedCredentials(image)
if err != nil {
klog.Errorf("Failed to get cached docker config: %v", err)
return credentialprovider.DockerConfig{}
}
if found {
return cachedConfig
}
response, err := p.plugin.ExecPlugin(context.Background(), image)
if err != nil {
klog.Errorf("Failed getting credential from external registry credential provider: %v", err)
return credentialprovider.DockerConfig{}
}
var cacheKey string
switch cacheKeyType := response.CacheKeyType; cacheKeyType {
case credentialproviderapi.ImagePluginCacheKeyType:
cacheKey = image
case credentialproviderapi.RegistryPluginCacheKeyType:
registry := parseRegistry(image)
cacheKey = registry
case credentialproviderapi.GlobalPluginCacheKeyType:
cacheKey = globalCacheKey
default:
klog.Errorf("credential provider plugin did not return a valid cacheKeyType: %q", cacheKeyType)
return credentialprovider.DockerConfig{}
}
dockerConfig := make(credentialprovider.DockerConfig, len(response.Auth))
for matchImage, authConfig := range response.Auth {
dockerConfig[matchImage] = credentialprovider.DockerConfigEntry{
Username: authConfig.Username,
Password: authConfig.Password,
}
}
// cache duration was explicitly 0 so don't cache this response at all.
if response.CacheDuration != nil && response.CacheDuration.Duration == 0 {
return dockerConfig
}
var expiresAt time.Time
// nil cache duration means use the default cache duration
if response.CacheDuration == nil {
if p.defaultCacheDuration == 0 {
return dockerConfig
}
expiresAt = time.Now().Add(p.defaultCacheDuration)
} else {
expiresAt = time.Now().Add(response.CacheDuration.Duration)
}
cachedEntry := &cacheEntry{
key: cacheKey,
credentials: dockerConfig,
expiresAt: expiresAt,
}
if err := p.cache.Add(cachedEntry); err != nil {
klog.Errorf("Error adding auth entry to cache: %v", err)
}
return dockerConfig
}
// Enabled always returns true since registration of the plugin via kubelet implies it should be enabled.
func (e *pluginProvider) Enabled() bool {
return true
}
// isImageAllowed returns true if the image matches against the list of allowed matches by the plugin.
func (p *pluginProvider) isImageAllowed(image string) bool {
for _, matchImage := range p.matchImages {
if matched, _ := credentialprovider.URLsMatchStr(matchImage, image); matched {
return true
}
}
return false
}
// getCachedCredentials returns a credentialprovider.DockerConfig if cached from the plugin.
func (p *pluginProvider) getCachedCredentials(image string) (credentialprovider.DockerConfig, bool, error) {
obj, found, err := p.cache.GetByKey(image)
if err != nil {
return nil, false, err
}
if found {
return obj.(*cacheEntry).credentials, true, nil
}
registry := parseRegistry(image)
obj, found, err = p.cache.GetByKey(registry)
if err != nil {
return nil, false, err
}
if found {
return obj.(*cacheEntry).credentials, true, nil
}
obj, found, err = p.cache.GetByKey(globalCacheKey)
if err != nil {
return nil, false, err
}
if found {
return obj.(*cacheEntry).credentials, true, nil
}
return nil, false, nil
}
// Plugin is the interface calling ExecPlugin. This is mainly for testability
// so tests don't have to actually exec any processes.
type Plugin interface {
ExecPlugin(ctx context.Context, image string) (*credentialproviderapi.CredentialProviderResponse, error)
}
// execPlugin is the implementation of the Plugin interface that execs a credential provider plugin based
// on it's name provided in CredentialProviderConfig. It is assumed that the executable is available in the
// plugin directory provided by the kubelet.
type execPlugin struct {
name string
apiVersion string
encoder runtime.Encoder
args []string
envVars []kubeletconfig.ExecEnvVar
pluginBinDir string
}
// ExecPlugin executes the plugin binary with arguments and environment variables specified in CredentialProviderConfig:
//
// $ ENV_NAME=ENV_VALUE <plugin-name> args[0] args[1] <<<request
//
// The plugin is expected to receive the CredentialProviderRequest API via stdin from the kubelet and
// return CredentialProviderResponse via stdout.
func (e *execPlugin) ExecPlugin(ctx context.Context, image string) (*credentialproviderapi.CredentialProviderResponse, error) {
authRequest := &credentialproviderapi.CredentialProviderRequest{Image: image}
data, err := e.encodeRequest(authRequest)
if err != nil {
return nil, fmt.Errorf("failed to encode auth request: %v", err)
}
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
stdin := bytes.NewBuffer(data)
// Use a catch-all timeout of 1 minute for all exec-based plugins, this should leave enough
// head room in case a plugin needs to retry a failed request while ensuring an exec plugin
// does not run forever. In the future we may want this timeout to be tweakable from the plugin
// config file.
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, filepath.Join(e.pluginBinDir, e.name), e.args...)
cmd.Stdout, cmd.Stderr, cmd.Stdin = stdout, stderr, stdin
cmd.Env = []string{}
for _, envVar := range e.envVars {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envVar.Name, envVar.Value))
}
err = cmd.Run()
if ctx.Err() != nil {
return nil, fmt.Errorf("error execing credential provider plugin %s for image %s: %w", e.name, image, ctx.Err())
}
if err != nil {
klog.V(2).Infof("Error execing credential provider plugin, stderr: %v", stderr.String())
return nil, fmt.Errorf("error execing credential provider plugin %s for image %s: %w", e.name, image, err)
}
data = stdout.Bytes()
// check that the response apiVersion matches what is expected
gvk, err := json.DefaultMetaFactory.Interpret(data)
if err != nil {
return nil, fmt.Errorf("error reading GVK from response: %w", err)
}
if gvk.GroupVersion().String() != e.apiVersion {
return nil, errors.New("apiVersion from credential plugin response did not match")
}
response, err := e.decodeResponse(stdout.Bytes())
if err != nil {
// err is explicitly not wrapped since it may contain credentials in the response.
return nil, errors.New("error decoding credential provider plugin response from stdout")
}
return response, nil
}
// encodeRequest encodes the internal CredentialProviderRequest type into the v1alpha1 version in json
func (e *execPlugin) encodeRequest(request *credentialproviderapi.CredentialProviderRequest) ([]byte, error) {
data, err := runtime.Encode(e.encoder, request)
if err != nil {
return nil, fmt.Errorf("error encoding request: %v", err)
}
return data, nil
}
// decodeResponse decodes data into the internal CredentialProviderResponse type
func (e *execPlugin) decodeResponse(data []byte) (*credentialproviderapi.CredentialProviderResponse, error) {
obj, gvk, err := codecs.UniversalDecoder().Decode(data, nil, nil)
if err != nil {
return nil, err
}
if gvk.Kind != "CredentialProviderResponse" {
return nil, fmt.Errorf("failed to decode CredentialProviderResponse, unexpected Kind: %q", gvk.Kind)
}
if gvk.Group != credentialproviderapi.GroupName {
return nil, fmt.Errorf("failed to decode CredentialProviderResponse, unexpected Group: %s", gvk.Group)
}
if internalResponse, ok := obj.(*credentialproviderapi.CredentialProviderResponse); ok {
return internalResponse, nil
}
return nil, fmt.Errorf("unable to convert %T to *CredentialProviderResponse", obj)
}
// parseRegistry extracts the registry hostname of an image (including port if specified).
func parseRegistry(image string) string {
imageParts := strings.Split(image, "/")
return imageParts[0]
}

View File

@ -0,0 +1,505 @@
/*
Copyright 2020 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 plugin
import (
"context"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
credentialproviderapi "k8s.io/kubelet/pkg/apis/credentialprovider"
credentialproviderv1alpha1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1"
"k8s.io/kubernetes/pkg/credentialprovider"
)
type fakeExecPlugin struct {
cacheKeyType credentialproviderapi.PluginCacheKeyType
cacheDuration time.Duration
auth map[string]credentialproviderapi.AuthConfig
}
func (f *fakeExecPlugin) ExecPlugin(ctx context.Context, image string) (*credentialproviderapi.CredentialProviderResponse, error) {
return &credentialproviderapi.CredentialProviderResponse{
CacheKeyType: f.cacheKeyType,
CacheDuration: &metav1.Duration{
Duration: f.cacheDuration,
},
Auth: f.auth,
}, nil
}
func Test_Provide(t *testing.T) {
testcases := []struct {
name string
pluginProvider *pluginProvider
image string
dockerconfig credentialprovider.DockerConfig
}{
{
name: "exact image match, with Registry cache key",
pluginProvider: &pluginProvider{
matchImages: []string{"test.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
auth: map[string]credentialproviderapi.AuthConfig{
"test.registry.io": {
Username: "user",
Password: "password",
},
},
},
},
image: "test.registry.io/foo/bar",
dockerconfig: credentialprovider.DockerConfig{
"test.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
},
},
{
name: "exact image match, with Image cache key",
pluginProvider: &pluginProvider{
matchImages: []string{"test.registry.io/foo/bar"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
auth: map[string]credentialproviderapi.AuthConfig{
"test.registry.io/foo/bar": {
Username: "user",
Password: "password",
},
},
},
},
image: "test.registry.io/foo/bar",
dockerconfig: credentialprovider.DockerConfig{
"test.registry.io/foo/bar": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
},
},
{
name: "exact image match, with Global cache key",
pluginProvider: &pluginProvider{
matchImages: []string{"test.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
auth: map[string]credentialproviderapi.AuthConfig{
"test.registry.io": {
Username: "user",
Password: "password",
},
},
},
},
image: "test.registry.io",
dockerconfig: credentialprovider.DockerConfig{
"test.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
},
},
{
name: "wild card image match, with Registry cache key",
pluginProvider: &pluginProvider{
matchImages: []string{"*.registry.io:8080"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io:8080": {
Username: "user",
Password: "password",
},
},
},
},
image: "test.registry.io:8080/foo",
dockerconfig: credentialprovider.DockerConfig{
"*.registry.io:8080": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
},
},
{
name: "wild card image match, with Image cache key",
pluginProvider: &pluginProvider{
matchImages: []string{"*.*.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
auth: map[string]credentialproviderapi.AuthConfig{
"*.*.registry.io": {
Username: "user",
Password: "password",
},
},
},
},
image: "foo.bar.registry.io/foo/bar",
dockerconfig: credentialprovider.DockerConfig{
"*.*.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
},
},
{
name: "wild card image match, with Global cache key",
pluginProvider: &pluginProvider{
matchImages: []string{"*.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io": {
Username: "user",
Password: "password",
},
},
},
},
image: "test.registry.io",
dockerconfig: credentialprovider.DockerConfig{
"*.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
},
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
dockerconfig := testcase.pluginProvider.Provide(testcase.image)
if !reflect.DeepEqual(dockerconfig, testcase.dockerconfig) {
t.Logf("actual docker config: %v", dockerconfig)
t.Logf("expected docker config: %v", testcase.dockerconfig)
t.Error("unexpected docker config")
}
})
}
}
func Test_encodeRequest(t *testing.T) {
testcases := []struct {
name string
apiVersion string
request *credentialproviderapi.CredentialProviderRequest
expectedData []byte
expectedErr bool
}{
{
name: "successful",
request: &credentialproviderapi.CredentialProviderRequest{
Image: "test.registry.io/foobar",
},
expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","image":"test.registry.io/foobar"}
`),
expectedErr: false,
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
mediaType := "application/json"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
t.Fatalf("unsupported media type: %s", mediaType)
}
e := &execPlugin{
encoder: codecs.EncoderForVersion(info.Serializer, credentialproviderv1alpha1.SchemeGroupVersion),
}
data, err := e.encodeRequest(testcase.request)
if err != nil && !testcase.expectedErr {
t.Fatalf("unexpected error: %v", err)
}
if err == nil && testcase.expectedErr {
t.Fatalf("expected error %v but got nil", testcase.expectedErr)
}
if !reflect.DeepEqual(data, testcase.expectedData) {
t.Errorf("actual encoded data: %v", string(data))
t.Errorf("expected encoded data: %v", string(testcase.expectedData))
t.Errorf("unexpected encoded response")
}
})
}
}
func Test_decodeResponse(t *testing.T) {
testcases := []struct {
name string
data []byte
expectedResponse *credentialproviderapi.CredentialProviderResponse
expectedErr bool
}{
{
name: "success",
data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
expectedResponse: &credentialproviderapi.CredentialProviderResponse{
CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
CacheDuration: &metav1.Duration{
Duration: time.Minute,
},
Auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io": {
Username: "user",
Password: "password",
},
},
},
expectedErr: false,
},
{
name: "wrong Kind",
data: []byte(`{"kind":"WrongKind","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
expectedResponse: nil,
expectedErr: true,
},
{
name: "wrong Group",
data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"foobar.kubelet.k8s.io/v1alpha1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
expectedResponse: nil,
expectedErr: true,
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
e := &execPlugin{}
decodedResponse, err := e.decodeResponse(testcase.data)
if err != nil && !testcase.expectedErr {
t.Fatalf("unexpected error: %v", err)
}
if err == nil && testcase.expectedErr {
t.Fatalf("expected error %v but not nil", testcase.expectedErr)
}
if !reflect.DeepEqual(decodedResponse, testcase.expectedResponse) {
t.Logf("actual decoded response: %#v", decodedResponse)
t.Logf("expected decoded response: %#v", testcase.expectedResponse)
t.Errorf("unexpected decoded response")
}
})
}
}
func Test_RegistryCacheKeyType(t *testing.T) {
pluginProvider := &pluginProvider{
matchImages: []string{"*.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
cacheDuration: time.Hour,
auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io": {
Username: "user",
Password: "password",
},
},
},
}
expectedDockerConfig := credentialprovider.DockerConfig{
"*.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
}
dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
expectedCacheKeys := []string{"test.registry.io"}
cacheKeys := pluginProvider.cache.ListKeys()
if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
t.Logf("actual cache keys: %v", cacheKeys)
t.Logf("expected cache keys: %v", expectedCacheKeys)
t.Error("unexpected cache keys")
}
// nil out the exec plugin, this will test whether credentialproviderapi are fetched
// from cache, otherwise Provider should panic
pluginProvider.plugin = nil
dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
}
func Test_ImageCacheKeyType(t *testing.T) {
pluginProvider := &pluginProvider{
matchImages: []string{"*.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
cacheDuration: time.Hour,
auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io": {
Username: "user",
Password: "password",
},
},
},
}
expectedDockerConfig := credentialprovider.DockerConfig{
"*.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
}
dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
expectedCacheKeys := []string{"test.registry.io/foo/bar"}
cacheKeys := pluginProvider.cache.ListKeys()
if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
t.Logf("actual cache keys: %v", cacheKeys)
t.Logf("expected cache keys: %v", expectedCacheKeys)
t.Error("unexpected cache keys")
}
// nil out the exec plugin, this will test whether credentialproviderapi are fetched
// from cache, otherwise Provider should panic
pluginProvider.plugin = nil
dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
}
func Test_GlobalCacheKeyType(t *testing.T) {
pluginProvider := &pluginProvider{
matchImages: []string{"*.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
cacheDuration: time.Hour,
auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io": {
Username: "user",
Password: "password",
},
},
},
}
expectedDockerConfig := credentialprovider.DockerConfig{
"*.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
}
dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
expectedCacheKeys := []string{"global"}
cacheKeys := pluginProvider.cache.ListKeys()
if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
t.Logf("actual cache keys: %v", cacheKeys)
t.Logf("expected cache keys: %v", expectedCacheKeys)
t.Error("unexpected cache keys")
}
// nil out the exec plugin, this will test whether credentialproviderapi are fetched
// from cache, otherwise Provider should panic
pluginProvider.plugin = nil
dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
}
func Test_NoCacheResponse(t *testing.T) {
pluginProvider := &pluginProvider{
matchImages: []string{"*.registry.io"},
cache: cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{}),
plugin: &fakeExecPlugin{
cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
cacheDuration: 0, // no cache
auth: map[string]credentialproviderapi.AuthConfig{
"*.registry.io": {
Username: "user",
Password: "password",
},
},
},
}
expectedDockerConfig := credentialprovider.DockerConfig{
"*.registry.io": credentialprovider.DockerConfigEntry{
Username: "user",
Password: "password",
},
}
dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
t.Logf("actual docker config: %v", dockerConfig)
t.Logf("expected docker config: %v", expectedDockerConfig)
t.Fatal("unexpected docker config")
}
expectedCacheKeys := []string{}
cacheKeys := pluginProvider.cache.ListKeys()
if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
t.Logf("actual cache keys: %v", cacheKeys)
t.Logf("expected cache keys: %v", expectedCacheKeys)
t.Error("unexpected cache keys")
}
}

View File

@ -686,6 +686,12 @@ const (
// may depend on old behavior where exec probe timeouts were ignored.
// Lock to default in v1.21 and remove in v1.22.
ExecProbeTimeout featuregate.Feature = "ExecProbeTimeout"
// owner: @andrewsykim
// alpha: v1.20
//
// Enable kubelet exec plugins for image pull credentials.
KubeletCredentialProviders featuregate.Feature = "KubeletCredentialProviders"
)
func init() {
@ -789,6 +795,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
SizeMemoryBackedVolumes: {Default: false, PreRelease: featuregate.Alpha},
LoadBalancerIPMode: {Default: false, PreRelease: featuregate.Alpha},
ExecProbeTimeout: {Default: true, PreRelease: featuregate.GA}, // lock to default in v1.21 and remove in v1.22
KubeletCredentialProviders: {Default: false, PreRelease: featuregate.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:

View File

@ -38,6 +38,7 @@ filegroup(
":package-srcs",
"//pkg/kubelet/apis/config/fuzzer:all-srcs",
"//pkg/kubelet/apis/config/scheme:all-srcs",
"//pkg/kubelet/apis/config/v1alpha1:all-srcs",
"//pkg/kubelet/apis/config/v1beta1:all-srcs",
"//pkg/kubelet/apis/config/validation:all-srcs",
],

View File

@ -1,6 +1,9 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- mtaufen
- api-approvers
reviewers:
- sig-node-reviewers
- sig-node-api-reviewers

View File

@ -39,6 +39,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&KubeletConfiguration{},
&SerializedNodeConfigSource{},
&CredentialProviderConfig{},
)
return nil
}

View File

@ -440,3 +440,79 @@ type SerializedNodeConfigSource struct {
// +optional
Source v1.NodeConfigSource
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CredentialProviderConfig is the configuration containing information about
// each exec credential provider. Kubelet reads this configuration from disk and enables
// each provider as specified by the CredentialProvider type.
type CredentialProviderConfig struct {
metav1.TypeMeta
// providers is a list of credential provider plugins that will be enabled by the kubelet.
// Multiple providers may match against a single image, in which case credentials
// from all providers will be returned to the kubelet. If multiple providers are called
// for a single image, the results are combined. If providers return overlapping
// auth keys, the value from the provider earlier in this list is used.
Providers []CredentialProvider
}
// CredentialProvider represents an exec plugin to be invoked by the kubelet. The plugin is only
// invoked when an image being pulled matches the images handled by the plugin (see matchImages).
type CredentialProvider struct {
// name is the required name of the credential provider. It must match the name of the
// provider executable as seen by the kubelet. The executable must be in the kubelet's
// bin directory (set by the --credential-provider-bin-dir flag).
Name string
// matchImages is a required list of strings used to match against images in order to
// determine if this provider should be invoked. If one of the strings matches the
// requested image from the kubelet, the plugin will be invoked and given a chance
// to provide credentials. Images are expected to contain the registry domain
// and URL path.
//
// Each entry in matchImages is a pattern which can optionally contain a port and a path.
// Globs can be used in the domain, but not in the port or the path. Globs are supported
// as subdomains like '*.k8s.io' or 'k8s.*.io', and top-level-domains such as 'k8s.*'.
// Matching partial subdomains like 'app*.k8s.io' is also supported. Each glob can only match
// a single subdomain segment, so *.io does not match *.k8s.io.
//
// A match exists between an image and a matchImage when all of the below are true:
// - Both contain the same number of domain parts and each part matches.
// - The URL path of an imageMatch must be a prefix of the target image URL path.
// - If the imageMatch contains a port, then the port must match in the image as well.
//
// Example values of matchImages:
// - 123456789.dkr.ecr.us-east-1.amazonaws.com
// - *.azurecr.io
// - gcr.io
// - *.*.registry.io
// - registry.io:8080/path
MatchImages []string
// defaultCacheDuration is the default duration the plugin will cache credentials in-memory
// if a cache duration is not provided in the plugin response. This field is required.
DefaultCacheDuration *metav1.Duration
// Required input version of the exec CredentialProviderRequest. The returned CredentialProviderResponse
// MUST use the same encoding version as the input. Current supported values are:
// - credentialprovider.kubelet.k8s.io/v1alpha1
APIVersion string
// Arguments to pass to the command when executing it.
// +optional
Args []string
// Env defines additional environment variables to expose to the process. These
// are unioned with the host's environment, as well as variables client-go uses
// to pass argument to the plugin.
// +optional
Env []ExecEnvVar
}
// ExecEnvVar is used for setting environment variables when executing an exec-based
// credential plugin.
type ExecEnvVar struct {
Name string
Value string
}

View File

@ -0,0 +1,36 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"register.go",
"zz_generated.conversion.go",
"zz_generated.deepcopy.go",
"zz_generated.defaults.go",
],
importpath = "k8s.io/kubernetes/pkg/kubelet/apis/config/v1alpha1",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubelet/apis/config:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/kubelet/config/v1alpha1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,24 @@
/*
Copyright 2020 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.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/kubelet/apis/config
// +k8s:conversion-gen-external-types=k8s.io/kubelet/config/v1alpha1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=../../../../../vendor/k8s.io/kubelet/config/v1alpha1
// +groupName=kubelet.config.k8s.io
package v1alpha1 // import "k8s.io/kubernetes/pkg/kubelet/apis/config/v1alpha1"

View File

@ -0,0 +1,36 @@
/*
Copyright 2020 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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
kubeletconfigv1alpha1 "k8s.io/kubelet/config/v1alpha1"
)
// GroupName is the group name used in this package
const GroupName = "kubelet.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
var (
// localSchemeBuilder extends the SchemeBuilder instance with the external types. In this package,
// defaulting and conversion init funcs are registered as well.
localSchemeBuilder = &kubeletconfigv1alpha1.SchemeBuilder
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = localSchemeBuilder.AddToScheme
)

View File

@ -0,0 +1,143 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
unsafe "unsafe"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
v1alpha1 "k8s.io/kubelet/config/v1alpha1"
config "k8s.io/kubernetes/pkg/kubelet/apis/config"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*v1alpha1.CredentialProvider)(nil), (*config.CredentialProvider)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CredentialProvider_To_config_CredentialProvider(a.(*v1alpha1.CredentialProvider), b.(*config.CredentialProvider), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CredentialProvider)(nil), (*v1alpha1.CredentialProvider)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CredentialProvider_To_v1alpha1_CredentialProvider(a.(*config.CredentialProvider), b.(*v1alpha1.CredentialProvider), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1alpha1.CredentialProviderConfig)(nil), (*config.CredentialProviderConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CredentialProviderConfig_To_config_CredentialProviderConfig(a.(*v1alpha1.CredentialProviderConfig), b.(*config.CredentialProviderConfig), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CredentialProviderConfig)(nil), (*v1alpha1.CredentialProviderConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CredentialProviderConfig_To_v1alpha1_CredentialProviderConfig(a.(*config.CredentialProviderConfig), b.(*v1alpha1.CredentialProviderConfig), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1alpha1.ExecEnvVar)(nil), (*config.ExecEnvVar)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_ExecEnvVar_To_config_ExecEnvVar(a.(*v1alpha1.ExecEnvVar), b.(*config.ExecEnvVar), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.ExecEnvVar)(nil), (*v1alpha1.ExecEnvVar)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ExecEnvVar_To_v1alpha1_ExecEnvVar(a.(*config.ExecEnvVar), b.(*v1alpha1.ExecEnvVar), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_CredentialProvider_To_config_CredentialProvider(in *v1alpha1.CredentialProvider, out *config.CredentialProvider, s conversion.Scope) error {
out.Name = in.Name
out.MatchImages = *(*[]string)(unsafe.Pointer(&in.MatchImages))
out.DefaultCacheDuration = (*v1.Duration)(unsafe.Pointer(in.DefaultCacheDuration))
out.APIVersion = in.APIVersion
out.Args = *(*[]string)(unsafe.Pointer(&in.Args))
out.Env = *(*[]config.ExecEnvVar)(unsafe.Pointer(&in.Env))
return nil
}
// Convert_v1alpha1_CredentialProvider_To_config_CredentialProvider is an autogenerated conversion function.
func Convert_v1alpha1_CredentialProvider_To_config_CredentialProvider(in *v1alpha1.CredentialProvider, out *config.CredentialProvider, s conversion.Scope) error {
return autoConvert_v1alpha1_CredentialProvider_To_config_CredentialProvider(in, out, s)
}
func autoConvert_config_CredentialProvider_To_v1alpha1_CredentialProvider(in *config.CredentialProvider, out *v1alpha1.CredentialProvider, s conversion.Scope) error {
out.Name = in.Name
out.MatchImages = *(*[]string)(unsafe.Pointer(&in.MatchImages))
out.DefaultCacheDuration = (*v1.Duration)(unsafe.Pointer(in.DefaultCacheDuration))
out.APIVersion = in.APIVersion
out.Args = *(*[]string)(unsafe.Pointer(&in.Args))
out.Env = *(*[]v1alpha1.ExecEnvVar)(unsafe.Pointer(&in.Env))
return nil
}
// Convert_config_CredentialProvider_To_v1alpha1_CredentialProvider is an autogenerated conversion function.
func Convert_config_CredentialProvider_To_v1alpha1_CredentialProvider(in *config.CredentialProvider, out *v1alpha1.CredentialProvider, s conversion.Scope) error {
return autoConvert_config_CredentialProvider_To_v1alpha1_CredentialProvider(in, out, s)
}
func autoConvert_v1alpha1_CredentialProviderConfig_To_config_CredentialProviderConfig(in *v1alpha1.CredentialProviderConfig, out *config.CredentialProviderConfig, s conversion.Scope) error {
out.Providers = *(*[]config.CredentialProvider)(unsafe.Pointer(&in.Providers))
return nil
}
// Convert_v1alpha1_CredentialProviderConfig_To_config_CredentialProviderConfig is an autogenerated conversion function.
func Convert_v1alpha1_CredentialProviderConfig_To_config_CredentialProviderConfig(in *v1alpha1.CredentialProviderConfig, out *config.CredentialProviderConfig, s conversion.Scope) error {
return autoConvert_v1alpha1_CredentialProviderConfig_To_config_CredentialProviderConfig(in, out, s)
}
func autoConvert_config_CredentialProviderConfig_To_v1alpha1_CredentialProviderConfig(in *config.CredentialProviderConfig, out *v1alpha1.CredentialProviderConfig, s conversion.Scope) error {
out.Providers = *(*[]v1alpha1.CredentialProvider)(unsafe.Pointer(&in.Providers))
return nil
}
// Convert_config_CredentialProviderConfig_To_v1alpha1_CredentialProviderConfig is an autogenerated conversion function.
func Convert_config_CredentialProviderConfig_To_v1alpha1_CredentialProviderConfig(in *config.CredentialProviderConfig, out *v1alpha1.CredentialProviderConfig, s conversion.Scope) error {
return autoConvert_config_CredentialProviderConfig_To_v1alpha1_CredentialProviderConfig(in, out, s)
}
func autoConvert_v1alpha1_ExecEnvVar_To_config_ExecEnvVar(in *v1alpha1.ExecEnvVar, out *config.ExecEnvVar, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
// Convert_v1alpha1_ExecEnvVar_To_config_ExecEnvVar is an autogenerated conversion function.
func Convert_v1alpha1_ExecEnvVar_To_config_ExecEnvVar(in *v1alpha1.ExecEnvVar, out *config.ExecEnvVar, s conversion.Scope) error {
return autoConvert_v1alpha1_ExecEnvVar_To_config_ExecEnvVar(in, out, s)
}
func autoConvert_config_ExecEnvVar_To_v1alpha1_ExecEnvVar(in *config.ExecEnvVar, out *v1alpha1.ExecEnvVar, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
// Convert_config_ExecEnvVar_To_v1alpha1_ExecEnvVar is an autogenerated conversion function.
func Convert_config_ExecEnvVar_To_v1alpha1_ExecEnvVar(in *config.ExecEnvVar, out *v1alpha1.ExecEnvVar, s conversion.Scope) error {
return autoConvert_config_ExecEnvVar_To_v1alpha1_ExecEnvVar(in, out, s)
}

View File

@ -0,0 +1,21 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1

View File

@ -0,0 +1,32 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -21,9 +21,94 @@ limitations under the License.
package config
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProvider) DeepCopyInto(out *CredentialProvider) {
*out = *in
if in.MatchImages != nil {
in, out := &in.MatchImages, &out.MatchImages
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DefaultCacheDuration != nil {
in, out := &in.DefaultCacheDuration, &out.DefaultCacheDuration
*out = new(v1.Duration)
**out = **in
}
if in.Args != nil {
in, out := &in.Args, &out.Args
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]ExecEnvVar, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProvider.
func (in *CredentialProvider) DeepCopy() *CredentialProvider {
if in == nil {
return nil
}
out := new(CredentialProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProviderConfig) DeepCopyInto(out *CredentialProviderConfig) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Providers != nil {
in, out := &in.Providers, &out.Providers
*out = make([]CredentialProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProviderConfig.
func (in *CredentialProviderConfig) DeepCopy() *CredentialProviderConfig {
if in == nil {
return nil
}
out := new(CredentialProviderConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CredentialProviderConfig) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExecEnvVar) DeepCopyInto(out *ExecEnvVar) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecEnvVar.
func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
if in == nil {
return nil
}
out := new(ExecEnvVar)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeletAnonymousAuthentication) DeepCopyInto(out *KubeletAnonymousAuthentication) {
*out = *in

View File

@ -65,6 +65,20 @@ type ContainerRuntimeOptions struct {
// CNICacheDir is the full path of the directory in which CNI should store
// cache files
CNICacheDir string
// Image credential provider plugin options
// ImageCredentialProviderConfigFile is the path to the credential provider plugin config file.
// This config file is a specification for what credential providers are enabled and invokved
// by the kubelet. The plugin config should contain information about what plugin binary
// to execute and what container images the plugin should be called for.
// +optional
ImageCredentialProviderConfigFile string
// ImageCredentialProviderBinDir is the path to the directory where credential provider plugin
// binaries exist. The name of each plugin binary is expected to match the name of the plugin
// specified in imageCredentialProviderConfigFile.
// +optional
ImageCredentialProviderBinDir string
}
// AddFlags adds flags to the container runtime, according to ContainerRuntimeOptions.
@ -90,4 +104,8 @@ func (s *ContainerRuntimeOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.CNIBinDir, "cni-bin-dir", s.CNIBinDir, fmt.Sprintf("A comma-separated list of full paths of directories in which to search for CNI plugin binaries. %s", dockerOnlyWarning))
fs.StringVar(&s.CNICacheDir, "cni-cache-dir", s.CNICacheDir, fmt.Sprintf("The full path of the directory in which CNI should store cache files. %s", dockerOnlyWarning))
fs.Int32Var(&s.NetworkPluginMTU, "network-plugin-mtu", s.NetworkPluginMTU, fmt.Sprintf("The MTU to be passed to the network plugin, to override the default. Set to 0 to use the default 1460 MTU. %s", dockerOnlyWarning))
// Image credential provider settings.
fs.StringVar(&s.ImageCredentialProviderConfigFile, "image-credential-provider-config", s.ImageCredentialProviderConfigFile, "The path to the credential provider plugin config file.")
fs.StringVar(&s.ImageCredentialProviderBinDir, "image-credential-provider-bin-dir", s.ImageCredentialProviderBinDir, "The path to the directory where credential provider plugin binaries are located.")
}

View File

@ -338,6 +338,8 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
cloudProvider string,
certDirectory string,
rootDirectory string,
imageCredentialProviderConfigFile string,
imageCredentialProviderBinDir string,
registerNode bool,
registerWithTaints []api.Taint,
allowedUnsafeSysctls []string,
@ -600,6 +602,8 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
kubeCfg.SerializeImagePulls,
float32(kubeCfg.RegistryPullQPS),
int(kubeCfg.RegistryBurst),
imageCredentialProviderConfigFile,
imageCredentialProviderBinDir,
kubeCfg.CPUCFSQuota,
kubeCfg.CPUCFSQuotaPeriod,
kubeDeps.RemoteRuntimeService,

View File

@ -35,6 +35,7 @@ go_library(
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/credentialprovider/plugin:go_default_library",
"//pkg/credentialprovider/secrets:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubelet/cm:go_default_library",

View File

@ -40,6 +40,7 @@ import (
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/credentialprovider"
"k8s.io/kubernetes/pkg/credentialprovider/plugin"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/cm"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
@ -166,6 +167,8 @@ func NewKubeGenericRuntimeManager(
serializeImagePulls bool,
imagePullQPS float32,
imagePullBurst int,
imageCredentialProviderConfigFile string,
imageCredentialProviderBinDir string,
cpuCFSQuota bool,
cpuCFSQuotaPeriod metav1.Duration,
runtimeService internalapi.RuntimeService,
@ -187,7 +190,6 @@ func NewKubeGenericRuntimeManager(
runtimeHelper: runtimeHelper,
runtimeService: newInstrumentedRuntimeService(runtimeService),
imageService: newInstrumentedImageManagerService(imageService),
keyring: credentialprovider.NewDockerKeyring(),
internalLifecycle: internalLifecycle,
legacyLogProvider: legacyLogProvider,
logManager: logManager,
@ -225,6 +227,18 @@ func NewKubeGenericRuntimeManager(
}
}
if !utilfeature.DefaultFeatureGate.Enabled(features.KubeletCredentialProviders) && (imageCredentialProviderConfigFile != "" || imageCredentialProviderBinDir != "") {
klog.Warningf("Flags --image-credential-provider-config or --image-credential-provider-bin-dir were set but the feature gate %s was disabled, these flags will be ignored",
features.KubeletCredentialProviders)
}
if utilfeature.DefaultFeatureGate.Enabled(features.KubeletCredentialProviders) && (imageCredentialProviderConfigFile != "" || imageCredentialProviderBinDir != "") {
if err := plugin.RegisterCredentialProviderPlugins(imageCredentialProviderConfigFile, imageCredentialProviderBinDir); err != nil {
klog.Fatalf("Failed to register CRI auth plugins: %v", err)
}
}
kubeRuntimeManager.keyring = credentialprovider.NewDockerKeyring()
kubeRuntimeManager.imagePuller = images.NewImageManager(
kubecontainer.FilterEventRecorder(recorder),
kubeRuntimeManager,

View File

@ -210,6 +210,7 @@
- k8s.io/apimachinery
- k8s.io/klog
- k8s.io/component-base
- k8s.io/kubelet
- baseImportPath: "./vendor/k8s.io/cluster-bootstrap/"
allowedImports:

View File

@ -9,7 +9,9 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/kubelet/config/v1alpha1:all-srcs",
"//staging/src/k8s.io/kubelet/config/v1beta1:all-srcs",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider:all-srcs",
"//staging/src/k8s.io/kubelet/pkg/apis/deviceplugin/v1alpha:all-srcs",
"//staging/src/k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1:all-srcs",
"//staging/src/k8s.io/kubelet/pkg/apis/pluginregistration/v1:all-srcs",

View File

@ -0,0 +1,33 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"register.go",
"types.go",
"zz_generated.deepcopy.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/kubelet/config/v1alpha1",
importpath = "k8s.io/kubelet/config/v1alpha1",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,21 @@
/*
Copyright 2020 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.
*/
// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true
// +groupName=kubelet.config.k8s.io
package v1alpha1 // import "k8s.io/kubelet/config/v1alpha1"

View File

@ -0,0 +1,43 @@
/*
Copyright 2020 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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name used in this package
const GroupName = "kubelet.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
var (
// SchemeBuilder is the scheme builder with scheme init functions to run for this API package
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = SchemeBuilder.AddToScheme
)
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&CredentialProviderConfig{},
)
return nil
}

View File

@ -0,0 +1,97 @@
/*
Copyright 2020 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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CredentialProviderConfig is the configuration containing information about
// each exec credential provider. Kubelet reads this configuration from disk and enables
// each provider as specified by the CredentialProvider type.
type CredentialProviderConfig struct {
metav1.TypeMeta `json:",inline"`
// providers is a list of credential provider plugins that will be enabled by the kubelet.
// Multiple providers may match against a single image, in which case credentials
// from all providers will be returned to the kubelet. If multiple providers are called
// for a single image, the results are combined. If providers return overlapping
// auth keys, the value from the provider earlier in this list is used.
Providers []CredentialProvider `json:"providers"`
}
// CredentialProvider represents an exec plugin to be invoked by the kubelet. The plugin is only
// invoked when an image being pulled matches the images handled by the plugin (see matchImages).
type CredentialProvider struct {
// name is the required name of the credential provider. It must match the name of the
// provider executable as seen by the kubelet. The executable must be in the kubelet's
// bin directory (set by the --image-credential-provider-bin-dir flag).
Name string `json:"name"`
// matchImages is a required list of strings used to match against images in order to
// determine if this provider should be invoked. If one of the strings matches the
// requested image from the kubelet, the plugin will be invoked and given a chance
// to provide credentials. Images are expected to contain the registry domain
// and URL path.
//
// Each entry in matchImages is a pattern which can optionally contain a port and a path.
// Globs can be used in the domain, but not in the port or the path. Globs are supported
// as subdomains like '*.k8s.io' or 'k8s.*.io', and top-level-domains such as 'k8s.*'.
// Matching partial subdomains like 'app*.k8s.io' is also supported. Each glob can only match
// a single subdomain segment, so *.io does not match *.k8s.io.
//
// A match exists between an image and a matchImage when all of the below are true:
// - Both contain the same number of domain parts and each part matches.
// - The URL path of an imageMatch must be a prefix of the target image URL path.
// - If the imageMatch contains a port, then the port must match in the image as well.
//
// Example values of matchImages:
// - 123456789.dkr.ecr.us-east-1.amazonaws.com
// - *.azurecr.io
// - gcr.io
// - *.*.registry.io
// - registry.io:8080/path
MatchImages []string `json:"matchImages"`
// defaultCacheDuration is the default duration the plugin will cache credentials in-memory
// if a cache duration is not provided in the plugin response. This field is required.
DefaultCacheDuration *metav1.Duration `json:"defaultCacheDuration"`
// Required input version of the exec CredentialProviderRequest. The returned CredentialProviderResponse
// MUST use the same encoding version as the input. Current supported values are:
// - credentialprovider.kubelet.k8s.io/v1alpha1
APIVersion string `json:"apiVersion"`
// Arguments to pass to the command when executing it.
// +optional
Args []string `json:"args,omitempty"`
// Env defines additional environment variables to expose to the process. These
// are unioned with the host's environment, as well as variables client-go uses
// to pass argument to the plugin.
// +optional
Env []ExecEnvVar `json:"env,omitempty"`
}
// ExecEnvVar is used for setting environment variables when executing an exec-based
// credential plugin.
type ExecEnvVar struct {
Name string `json:"name"`
Value string `json:"value"`
}

View File

@ -0,0 +1,110 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProvider) DeepCopyInto(out *CredentialProvider) {
*out = *in
if in.MatchImages != nil {
in, out := &in.MatchImages, &out.MatchImages
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DefaultCacheDuration != nil {
in, out := &in.DefaultCacheDuration, &out.DefaultCacheDuration
*out = new(v1.Duration)
**out = **in
}
if in.Args != nil {
in, out := &in.Args, &out.Args
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]ExecEnvVar, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProvider.
func (in *CredentialProvider) DeepCopy() *CredentialProvider {
if in == nil {
return nil
}
out := new(CredentialProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProviderConfig) DeepCopyInto(out *CredentialProviderConfig) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Providers != nil {
in, out := &in.Providers, &out.Providers
*out = make([]CredentialProvider, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProviderConfig.
func (in *CredentialProviderConfig) DeepCopy() *CredentialProviderConfig {
if in == nil {
return nil
}
out := new(CredentialProviderConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CredentialProviderConfig) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExecEnvVar) DeepCopyInto(out *ExecEnvVar) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecEnvVar.
func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
if in == nil {
return nil
}
out := new(ExecEnvVar)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"register.go",
"types.go",
"zz_generated.deepcopy.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/kubelet/pkg/apis/credentialprovider",
importpath = "k8s.io/kubelet/pkg/apis/credentialprovider",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install:all-srcs",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- api-approvers
reviewers:
- sig-node-api-reviewers
- sig-auth-api-reviewers

View File

@ -0,0 +1,20 @@
/*
Copyright 2020 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.
*/
// +k8s:deepcopy-gen=package
// +groupName=credentialprovider.kubelet.k8s.io
package credentialprovider // import "k8s.io/kubelet/pkg/apis/credentialprovider"

View File

@ -0,0 +1,29 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["install.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/kubelet/pkg/apis/credentialprovider/install",
importpath = "k8s.io/kubelet/pkg/apis/credentialprovider/install",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,31 @@
/*
Copyright 2020 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 install
import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubelet/pkg/apis/credentialprovider"
"k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1"
)
// Install registers the credentialprovider.kubelet.k8s.io APIs into the given scheme.
func Install(scheme *runtime.Scheme) {
utilruntime.Must(credentialprovider.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1alpha1.SchemeGroupVersion))
}

View File

@ -0,0 +1,41 @@
/*
Copyright 2020 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 (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "credentialprovider.kubelet.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&CredentialProviderRequest{},
&CredentialProviderResponse{},
)
return nil
}

View File

@ -0,0 +1,117 @@
/*
Copyright 2020 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 (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CredentialProviderRequest includes the image that the kubelet requires authentication for.
// Kubelet will pass this request object to the plugin via stdin. In general, plugins should
// prefer responding with the same apiVersion they were sent.
type CredentialProviderRequest struct {
metav1.TypeMeta
// image is the container image that is being pulled as part of the
// credential provider plugin request. Plugins may optionally parse the image
// to extract any information required to fetch credentials.
Image string
}
type PluginCacheKeyType string
const (
// ImagePluginCacheKeyType means the kubelet will cache credentials on a per-image basis,
// using the image passed from the kubelet directly as the cache key. This includes
// the registry domain, port (if specified), and path but does not include tags or SHAs.
ImagePluginCacheKeyType PluginCacheKeyType = "Image"
// RegistryPluginCacheKeyType means the kubelet will cache credentials on a per-registry basis.
// The cache key will be based on the registry domain and port (if present) parsed from the requested image.
RegistryPluginCacheKeyType PluginCacheKeyType = "Registry"
// GlobalPluginCacheKeyType means the kubelet will cache credentials for all images that
// match for a given plugin. This cache key should only be returned by plugins that do not use
// the image input at all.
GlobalPluginCacheKeyType PluginCacheKeyType = "Global"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CredentialProviderResponse holds credentials that the kubelet should use for the specified
// image provided in the original request. Kubelet will read the response from the plugin via stdout.
// This response should be set to the same apiVersion as CredentialProviderRequest.
type CredentialProviderResponse struct {
metav1.TypeMeta
// cacheKeyType indiciates the type of caching key to use based on the image provided
// in the request. There are three valid values for the cache key type: Image, Registry, and
// Global. If an invalid value is specified, the response will NOT be used by the kubelet.
CacheKeyType PluginCacheKeyType
// cacheDuration indicates the duration the provided credentials should be cached for.
// The kubelet will use this field to set the in-memory cache duration for credentials
// in the AuthConfig. If null, the kubelet will use defaultCacheDuration provided in
// CredentialProviderConfig. If set to 0, the kubelet will not cache the provided AuthConfig.
// +optional
CacheDuration *metav1.Duration
// auth is a map containing authentication information passed into the kubelet.
// Each key is a match image string (more on this below). The corresponding authConfig value
// should be valid for all images that match against this key. A plugin should set
// this field to null if no valid credentials can be returned for the requested image.
//
// Each key in the map is a pattern which can optionally contain a port and a path.
// Globs can be used in the domain, but not in the port or the path. Globs are supported
// as subdomains like '*.k8s.io' or 'k8s.*.io', and top-level-domains such as 'k8s.*'.
// Matching partial subdomains like 'app*.k8s.io' is also supported. Each glob can only match
// a single subdomain segment, so *.io does not match *.k8s.io.
//
// The kubelet will match images against the key when all of the below are true:
// - Both contain the same number of domain parts and each part matches.
// - The URL path of an imageMatch must be a prefix of the target image URL path.
// - If the imageMatch contains a port, then the port must match in the image as well.
//
// When multiple keys are returned, the kubelet will traverse all keys in reverse order so that:
// - longer keys come before shorter keys with the same prefix
// - non-wildcard keys come before wildcard keys with the same prefix.
//
// For any given match, the kubelet will attempt an image pull with the provided credentials,
// stopping after the first successfully authenticated pull.
//
// Example keys:
// - 123456789.dkr.ecr.us-east-1.amazonaws.com
// - *.azurecr.io
// - gcr.io
// - *.*.registry.io
// - registry.io:8080/path
// +optional
Auth map[string]AuthConfig
}
// AuthConfig contains authentication information for a container registry.
// Only username/password based authentication is supported today, but more authentication
// mechanisms may be added in the future.
type AuthConfig struct {
// username is the username used for authenticating to the container registry
// An empty username is valid.
Username string
// password is the password used for authenticating to the container registry
// An empty password is valid.
Password string
}

View File

@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"register.go",
"types.go",
"zz_generated.conversion.go",
"zz_generated.deepcopy.go",
"zz_generated.defaults.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1",
importpath = "k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/kubelet/pkg/apis/credentialprovider:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,22 @@
/*
Copyright 2020 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.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/kubelet/pkg/apis/credentialprovider
// +k8s:defaulter-gen=TypeMeta
// +groupName=credentialprovider.kubelet.k8s.io
package v1alpha1 // import "k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1"

View File

@ -0,0 +1,46 @@
/*
Copyright 2020 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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "credentialprovider.kubelet.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var (
SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
localSchemeBuilder = &SchemeBuilder
)
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&CredentialProviderRequest{},
&CredentialProviderResponse{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,117 @@
/*
Copyright 2020 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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CredentialProviderRequest includes the image that the kubelet requires authentication for.
// Kubelet will pass this request object to the plugin via stdin. In general, plugins should
// prefer responding with the same apiVersion they were sent.
type CredentialProviderRequest struct {
metav1.TypeMeta `json:",inline"`
// image is the container image that is being pulled as part of the
// credential provider plugin request. Plugins may optionally parse the image
// to extract any information required to fetch credentials.
Image string `json:"image"`
}
type PluginCacheKeyType string
const (
// ImagePluginCacheKeyType means the kubelet will cache credentials on a per-image basis,
// using the image passed from the kubelet directly as the cache key. This includes
// the registry domain, port (if specified), and path but does not include tags or SHAs.
ImagePluginCacheKeyType PluginCacheKeyType = "Image"
// RegistryPluginCacheKeyType means the kubelet will cache credentials on a per-registry basis.
// The cache key will be based on the registry domain and port (if present) parsed from the requested image.
RegistryPluginCacheKeyType PluginCacheKeyType = "Registry"
// GlobalPluginCacheKeyType means the kubelet will cache credentials for all images that
// match for a given plugin. This cache key should only be returned by plugins that do not use
// the image input at all.
GlobalPluginCacheKeyType PluginCacheKeyType = "Global"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CredentialProviderResponse holds credentials that the kubelet should use for the specified
// image provided in the original request. Kubelet will read the response from the plugin via stdout.
// This response should be set to the same apiVersion as CredentialProviderRequest.
type CredentialProviderResponse struct {
metav1.TypeMeta `json:",inline"`
// cacheKeyType indiciates the type of caching key to use based on the image provided
// in the request. There are three valid values for the cache key type: Image, Registry, and
// Global. If an invalid value is specified, the response will NOT be used by the kubelet.
CacheKeyType PluginCacheKeyType `json:"cacheKeyType"`
// cacheDuration indicates the duration the provided credentials should be cached for.
// The kubelet will use this field to set the in-memory cache duration for credentials
// in the AuthConfig. If null, the kubelet will use defaultCacheDuration provided in
// CredentialProviderConfig. If set to 0, the kubelet will not cache the provided AuthConfig.
// +optional
CacheDuration *metav1.Duration `json:"cacheDuration,omitempty"`
// auth is a map containing authentication information passed into the kubelet.
// Each key is a match image string (more on this below). The corresponding authConfig value
// should be valid for all images that match against this key. A plugin should set
// this field to null if no valid credentials can be returned for the requested image.
//
// Each key in the map is a pattern which can optionally contain a port and a path.
// Globs can be used in the domain, but not in the port or the path. Globs are supported
// as subdomains like '*.k8s.io' or 'k8s.*.io', and top-level-domains such as 'k8s.*'.
// Matching partial subdomains like 'app*.k8s.io' is also supported. Each glob can only match
// a single subdomain segment, so *.io does not match *.k8s.io.
//
// The kubelet will match images against the key when all of the below are true:
// - Both contain the same number of domain parts and each part matches.
// - The URL path of an imageMatch must be a prefix of the target image URL path.
// - If the imageMatch contains a port, then the port must match in the image as well.
//
// When multiple keys are returned, the kubelet will traverse all keys in reverse order so that:
// - longer keys come before shorter keys with the same prefix
// - non-wildcard keys come before wildcard keys with the same prefix.
//
// For any given match, the kubelet will attempt an image pull with the provided credentials,
// stopping after the first successfully authenticated pull.
//
// Example keys:
// - 123456789.dkr.ecr.us-east-1.amazonaws.com
// - *.azurecr.io
// - gcr.io
// - *.*.registry.io
// - registry.io:8080/path
// +optional
Auth map[string]AuthConfig `json:"auth,omitempty"`
}
// AuthConfig contains authentication information for a container registry.
// Only username/password based authentication is supported today, but more authentication
// mechanisms may be added in the future.
type AuthConfig struct {
// username is the username used for authenticating to the container registry
// An empty username is valid.
Username string `json:"username"`
// password is the password used for authenticating to the container registry
// An empty password is valid.
Password string `json:"password"`
}

View File

@ -0,0 +1,136 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
unsafe "unsafe"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
credentialprovider "k8s.io/kubelet/pkg/apis/credentialprovider"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*AuthConfig)(nil), (*credentialprovider.AuthConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_AuthConfig_To_credentialprovider_AuthConfig(a.(*AuthConfig), b.(*credentialprovider.AuthConfig), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*credentialprovider.AuthConfig)(nil), (*AuthConfig)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_credentialprovider_AuthConfig_To_v1alpha1_AuthConfig(a.(*credentialprovider.AuthConfig), b.(*AuthConfig), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CredentialProviderRequest)(nil), (*credentialprovider.CredentialProviderRequest)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CredentialProviderRequest_To_credentialprovider_CredentialProviderRequest(a.(*CredentialProviderRequest), b.(*credentialprovider.CredentialProviderRequest), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*credentialprovider.CredentialProviderRequest)(nil), (*CredentialProviderRequest)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_credentialprovider_CredentialProviderRequest_To_v1alpha1_CredentialProviderRequest(a.(*credentialprovider.CredentialProviderRequest), b.(*CredentialProviderRequest), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CredentialProviderResponse)(nil), (*credentialprovider.CredentialProviderResponse)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CredentialProviderResponse_To_credentialprovider_CredentialProviderResponse(a.(*CredentialProviderResponse), b.(*credentialprovider.CredentialProviderResponse), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*credentialprovider.CredentialProviderResponse)(nil), (*CredentialProviderResponse)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_credentialprovider_CredentialProviderResponse_To_v1alpha1_CredentialProviderResponse(a.(*credentialprovider.CredentialProviderResponse), b.(*CredentialProviderResponse), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_AuthConfig_To_credentialprovider_AuthConfig(in *AuthConfig, out *credentialprovider.AuthConfig, s conversion.Scope) error {
out.Username = in.Username
out.Password = in.Password
return nil
}
// Convert_v1alpha1_AuthConfig_To_credentialprovider_AuthConfig is an autogenerated conversion function.
func Convert_v1alpha1_AuthConfig_To_credentialprovider_AuthConfig(in *AuthConfig, out *credentialprovider.AuthConfig, s conversion.Scope) error {
return autoConvert_v1alpha1_AuthConfig_To_credentialprovider_AuthConfig(in, out, s)
}
func autoConvert_credentialprovider_AuthConfig_To_v1alpha1_AuthConfig(in *credentialprovider.AuthConfig, out *AuthConfig, s conversion.Scope) error {
out.Username = in.Username
out.Password = in.Password
return nil
}
// Convert_credentialprovider_AuthConfig_To_v1alpha1_AuthConfig is an autogenerated conversion function.
func Convert_credentialprovider_AuthConfig_To_v1alpha1_AuthConfig(in *credentialprovider.AuthConfig, out *AuthConfig, s conversion.Scope) error {
return autoConvert_credentialprovider_AuthConfig_To_v1alpha1_AuthConfig(in, out, s)
}
func autoConvert_v1alpha1_CredentialProviderRequest_To_credentialprovider_CredentialProviderRequest(in *CredentialProviderRequest, out *credentialprovider.CredentialProviderRequest, s conversion.Scope) error {
out.Image = in.Image
return nil
}
// Convert_v1alpha1_CredentialProviderRequest_To_credentialprovider_CredentialProviderRequest is an autogenerated conversion function.
func Convert_v1alpha1_CredentialProviderRequest_To_credentialprovider_CredentialProviderRequest(in *CredentialProviderRequest, out *credentialprovider.CredentialProviderRequest, s conversion.Scope) error {
return autoConvert_v1alpha1_CredentialProviderRequest_To_credentialprovider_CredentialProviderRequest(in, out, s)
}
func autoConvert_credentialprovider_CredentialProviderRequest_To_v1alpha1_CredentialProviderRequest(in *credentialprovider.CredentialProviderRequest, out *CredentialProviderRequest, s conversion.Scope) error {
out.Image = in.Image
return nil
}
// Convert_credentialprovider_CredentialProviderRequest_To_v1alpha1_CredentialProviderRequest is an autogenerated conversion function.
func Convert_credentialprovider_CredentialProviderRequest_To_v1alpha1_CredentialProviderRequest(in *credentialprovider.CredentialProviderRequest, out *CredentialProviderRequest, s conversion.Scope) error {
return autoConvert_credentialprovider_CredentialProviderRequest_To_v1alpha1_CredentialProviderRequest(in, out, s)
}
func autoConvert_v1alpha1_CredentialProviderResponse_To_credentialprovider_CredentialProviderResponse(in *CredentialProviderResponse, out *credentialprovider.CredentialProviderResponse, s conversion.Scope) error {
out.CacheKeyType = credentialprovider.PluginCacheKeyType(in.CacheKeyType)
out.CacheDuration = (*v1.Duration)(unsafe.Pointer(in.CacheDuration))
out.Auth = *(*map[string]credentialprovider.AuthConfig)(unsafe.Pointer(&in.Auth))
return nil
}
// Convert_v1alpha1_CredentialProviderResponse_To_credentialprovider_CredentialProviderResponse is an autogenerated conversion function.
func Convert_v1alpha1_CredentialProviderResponse_To_credentialprovider_CredentialProviderResponse(in *CredentialProviderResponse, out *credentialprovider.CredentialProviderResponse, s conversion.Scope) error {
return autoConvert_v1alpha1_CredentialProviderResponse_To_credentialprovider_CredentialProviderResponse(in, out, s)
}
func autoConvert_credentialprovider_CredentialProviderResponse_To_v1alpha1_CredentialProviderResponse(in *credentialprovider.CredentialProviderResponse, out *CredentialProviderResponse, s conversion.Scope) error {
out.CacheKeyType = PluginCacheKeyType(in.CacheKeyType)
out.CacheDuration = (*v1.Duration)(unsafe.Pointer(in.CacheDuration))
out.Auth = *(*map[string]AuthConfig)(unsafe.Pointer(&in.Auth))
return nil
}
// Convert_credentialprovider_CredentialProviderResponse_To_v1alpha1_CredentialProviderResponse is an autogenerated conversion function.
func Convert_credentialprovider_CredentialProviderResponse_To_v1alpha1_CredentialProviderResponse(in *credentialprovider.CredentialProviderResponse, out *CredentialProviderResponse, s conversion.Scope) error {
return autoConvert_credentialprovider_CredentialProviderResponse_To_v1alpha1_CredentialProviderResponse(in, out, s)
}

View File

@ -0,0 +1,104 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthConfig) DeepCopyInto(out *AuthConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthConfig.
func (in *AuthConfig) DeepCopy() *AuthConfig {
if in == nil {
return nil
}
out := new(AuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProviderRequest) DeepCopyInto(out *CredentialProviderRequest) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProviderRequest.
func (in *CredentialProviderRequest) DeepCopy() *CredentialProviderRequest {
if in == nil {
return nil
}
out := new(CredentialProviderRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CredentialProviderRequest) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProviderResponse) DeepCopyInto(out *CredentialProviderResponse) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.CacheDuration != nil {
in, out := &in.CacheDuration, &out.CacheDuration
*out = new(v1.Duration)
**out = **in
}
if in.Auth != nil {
in, out := &in.Auth, &out.Auth
*out = make(map[string]AuthConfig, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProviderResponse.
func (in *CredentialProviderResponse) DeepCopy() *CredentialProviderResponse {
if in == nil {
return nil
}
out := new(CredentialProviderResponse)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CredentialProviderResponse) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,32 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -0,0 +1,104 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package credentialprovider
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthConfig) DeepCopyInto(out *AuthConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthConfig.
func (in *AuthConfig) DeepCopy() *AuthConfig {
if in == nil {
return nil
}
out := new(AuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProviderRequest) DeepCopyInto(out *CredentialProviderRequest) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProviderRequest.
func (in *CredentialProviderRequest) DeepCopy() *CredentialProviderRequest {
if in == nil {
return nil
}
out := new(CredentialProviderRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CredentialProviderRequest) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CredentialProviderResponse) DeepCopyInto(out *CredentialProviderResponse) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.CacheDuration != nil {
in, out := &in.CacheDuration, &out.CacheDuration
*out = new(v1.Duration)
**out = **in
}
if in.Auth != nil {
in, out := &in.Auth, &out.Auth
*out = make(map[string]AuthConfig, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProviderResponse.
func (in *CredentialProviderResponse) DeepCopy() *CredentialProviderResponse {
if in == nil {
return nil
}
out := new(CredentialProviderResponse)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CredentialProviderResponse) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

4
vendor/modules.txt vendored
View File

@ -2426,7 +2426,11 @@ k8s.io/kubectl/pkg/validation
# k8s.io/kubelet v0.0.0 => ./staging/src/k8s.io/kubelet
## explicit
# k8s.io/kubelet => ./staging/src/k8s.io/kubelet
k8s.io/kubelet/config/v1alpha1
k8s.io/kubelet/config/v1beta1
k8s.io/kubelet/pkg/apis/credentialprovider
k8s.io/kubelet/pkg/apis/credentialprovider/install
k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1
k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1
k8s.io/kubelet/pkg/apis/pluginregistration/v1
k8s.io/kubelet/pkg/apis/podresources/v1