From 359c6e262371d6699b45c6b5c37378035e4a683b Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:06 -0500 Subject: [PATCH 1/9] kubelet: add CredentialProviderConfig API Signed-off-by: Andrew Sy Kim --- api/api-rules/violation_exceptions.list | 4 + build/kazel_generated.bzl | 2 + pkg/kubelet/apis/config/BUILD | 1 + pkg/kubelet/apis/config/register.go | 1 + pkg/kubelet/apis/config/types.go | 75 +++++++++ pkg/kubelet/apis/config/v1alpha1/BUILD | 36 +++++ pkg/kubelet/apis/config/v1alpha1/doc.go | 24 +++ pkg/kubelet/apis/config/v1alpha1/register.go | 36 +++++ .../v1alpha1/zz_generated.conversion.go | 143 ++++++++++++++++++ .../config/v1alpha1/zz_generated.deepcopy.go | 21 +++ .../config/v1alpha1/zz_generated.defaults.go | 32 ++++ .../apis/config/zz_generated.deepcopy.go | 85 +++++++++++ staging/src/k8s.io/kubelet/BUILD | 1 + .../src/k8s.io/kubelet/config/v1alpha1/BUILD | 33 ++++ .../src/k8s.io/kubelet/config/v1alpha1/doc.go | 21 +++ .../kubelet/config/v1alpha1/register.go | 43 ++++++ .../k8s.io/kubelet/config/v1alpha1/types.go | 96 ++++++++++++ .../config/v1alpha1/zz_generated.deepcopy.go | 110 ++++++++++++++ vendor/modules.txt | 1 + 19 files changed, 765 insertions(+) create mode 100644 pkg/kubelet/apis/config/v1alpha1/BUILD create mode 100644 pkg/kubelet/apis/config/v1alpha1/doc.go create mode 100644 pkg/kubelet/apis/config/v1alpha1/register.go create mode 100644 pkg/kubelet/apis/config/v1alpha1/zz_generated.conversion.go create mode 100644 pkg/kubelet/apis/config/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/kubelet/apis/config/v1alpha1/zz_generated.defaults.go create mode 100644 staging/src/k8s.io/kubelet/config/v1alpha1/BUILD create mode 100644 staging/src/k8s.io/kubelet/config/v1alpha1/doc.go create mode 100644 staging/src/k8s.io/kubelet/config/v1alpha1/register.go create mode 100644 staging/src/k8s.io/kubelet/config/v1alpha1/types.go create mode 100644 staging/src/k8s.io/kubelet/config/v1alpha1/zz_generated.deepcopy.go diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index 2a66aff049d..e8711528271 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -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 diff --git a/build/kazel_generated.bzl b/build/kazel_generated.bzl index 2414ce7c651..504ec608ea8 100644 --- a/build/kazel_generated.bzl +++ b/build/kazel_generated.bzl @@ -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"], diff --git a/pkg/kubelet/apis/config/BUILD b/pkg/kubelet/apis/config/BUILD index 53d67a8ee21..6ad87d3a15b 100644 --- a/pkg/kubelet/apis/config/BUILD +++ b/pkg/kubelet/apis/config/BUILD @@ -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", ], diff --git a/pkg/kubelet/apis/config/register.go b/pkg/kubelet/apis/config/register.go index cbebd990ab9..d13cc6bdb63 100644 --- a/pkg/kubelet/apis/config/register.go +++ b/pkg/kubelet/apis/config/register.go @@ -39,6 +39,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &KubeletConfiguration{}, &SerializedNodeConfigSource{}, + &CredentialProviderConfig{}, ) return nil } diff --git a/pkg/kubelet/apis/config/types.go b/pkg/kubelet/apis/config/types.go index a7e2d5e1b23..c7e5057257e 100644 --- a/pkg/kubelet/apis/config/types.go +++ b/pkg/kubelet/apis/config/types.go @@ -440,3 +440,78 @@ 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. + 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 +} diff --git a/pkg/kubelet/apis/config/v1alpha1/BUILD b/pkg/kubelet/apis/config/v1alpha1/BUILD new file mode 100644 index 00000000000..aa8a4973438 --- /dev/null +++ b/pkg/kubelet/apis/config/v1alpha1/BUILD @@ -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"], +) diff --git a/pkg/kubelet/apis/config/v1alpha1/doc.go b/pkg/kubelet/apis/config/v1alpha1/doc.go new file mode 100644 index 00000000000..84ed2ea1220 --- /dev/null +++ b/pkg/kubelet/apis/config/v1alpha1/doc.go @@ -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" diff --git a/pkg/kubelet/apis/config/v1alpha1/register.go b/pkg/kubelet/apis/config/v1alpha1/register.go new file mode 100644 index 00000000000..c673b524511 --- /dev/null +++ b/pkg/kubelet/apis/config/v1alpha1/register.go @@ -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 +) diff --git a/pkg/kubelet/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/kubelet/apis/config/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..b1be1643e13 --- /dev/null +++ b/pkg/kubelet/apis/config/v1alpha1/zz_generated.conversion.go @@ -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) +} diff --git a/pkg/kubelet/apis/config/v1alpha1/zz_generated.deepcopy.go b/pkg/kubelet/apis/config/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..0ec19467c40 --- /dev/null +++ b/pkg/kubelet/apis/config/v1alpha1/zz_generated.deepcopy.go @@ -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 diff --git a/pkg/kubelet/apis/config/v1alpha1/zz_generated.defaults.go b/pkg/kubelet/apis/config/v1alpha1/zz_generated.defaults.go new file mode 100644 index 00000000000..dd621a3acda --- /dev/null +++ b/pkg/kubelet/apis/config/v1alpha1/zz_generated.defaults.go @@ -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 +} diff --git a/pkg/kubelet/apis/config/zz_generated.deepcopy.go b/pkg/kubelet/apis/config/zz_generated.deepcopy.go index 87b3969325c..b54cad7e5e3 100644 --- a/pkg/kubelet/apis/config/zz_generated.deepcopy.go +++ b/pkg/kubelet/apis/config/zz_generated.deepcopy.go @@ -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 diff --git a/staging/src/k8s.io/kubelet/BUILD b/staging/src/k8s.io/kubelet/BUILD index 3cabbc615f7..e84c3d6bac7 100644 --- a/staging/src/k8s.io/kubelet/BUILD +++ b/staging/src/k8s.io/kubelet/BUILD @@ -9,6 +9,7 @@ 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/deviceplugin/v1alpha:all-srcs", "//staging/src/k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1:all-srcs", diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/BUILD b/staging/src/k8s.io/kubelet/config/v1alpha1/BUILD new file mode 100644 index 00000000000..8a0db88e5a8 --- /dev/null +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/BUILD @@ -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"], +) diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/doc.go b/staging/src/k8s.io/kubelet/config/v1alpha1/doc.go new file mode 100644 index 00000000000..0c2d9f1a247 --- /dev/null +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/doc.go @@ -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" diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/register.go b/staging/src/k8s.io/kubelet/config/v1alpha1/register.go new file mode 100644 index 00000000000..b12ce03ec03 --- /dev/null +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/register.go @@ -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 +} diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/types.go b/staging/src/k8s.io/kubelet/config/v1alpha1/types.go new file mode 100644 index 00000000000..d9b2f67efc1 --- /dev/null +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/types.go @@ -0,0 +1,96 @@ +/* +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. + 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"` +} diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/kubelet/config/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..43642020247 --- /dev/null +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/zz_generated.deepcopy.go @@ -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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c596c25ba89..4cd47a1552e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2426,6 +2426,7 @@ 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/deviceplugin/v1beta1 k8s.io/kubelet/pkg/apis/pluginregistration/v1 From c23638c3ce4857fecca020801c16359930078409 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:06 -0500 Subject: [PATCH 2/9] kubelet: update pkg/kubelet/apis/config/OWNERS to include api approvers and reviewers Signed-off-by: Andrew Sy Kim --- pkg/kubelet/apis/config/OWNERS | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/kubelet/apis/config/OWNERS b/pkg/kubelet/apis/config/OWNERS index b8b2acd3b66..70b1183c89d 100644 --- a/pkg/kubelet/apis/config/OWNERS +++ b/pkg/kubelet/apis/config/OWNERS @@ -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 From 51441fd052756d2c76daee83d39b352bd8a2d265 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:06 -0500 Subject: [PATCH 3/9] kubelet: support alpha credential provider exec plugins Signed-off-by: Andrew Sy Kim --- cmd/kubelet/app/server.go | 6 ++++++ pkg/kubelet/config/flags.go | 18 ++++++++++++++++++ pkg/kubelet/kubelet.go | 4 ++++ pkg/kubelet/kuberuntime/BUILD | 1 + pkg/kubelet/kuberuntime/kuberuntime_manager.go | 16 +++++++++++++++- 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 98d2794eaab..e5b099d5f30 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -1131,6 +1131,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, @@ -1205,6 +1207,8 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, cloudProvider string, certDirectory string, rootDirectory string, + imageCredentialProviderConfigFile string, + imageCredentialProviderBinDir string, registerNode bool, registerWithTaints []api.Taint, allowedUnsafeSysctls []string, @@ -1236,6 +1240,8 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, cloudProvider, certDirectory, rootDirectory, + imageCredentialProviderConfigFile, + imageCredentialProviderBinDir, registerNode, registerWithTaints, allowedUnsafeSysctls, diff --git a/pkg/kubelet/config/flags.go b/pkg/kubelet/config/flags.go index 79191255ae9..1fc61dcfca1 100644 --- a/pkg/kubelet/config/flags.go +++ b/pkg/kubelet/config/flags.go @@ -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.") } diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index d6d620790c0..7e4c9bf778a 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -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, diff --git a/pkg/kubelet/kuberuntime/BUILD b/pkg/kubelet/kuberuntime/BUILD index dc3c2ace105..6827346f5d1 100644 --- a/pkg/kubelet/kuberuntime/BUILD +++ b/pkg/kubelet/kuberuntime/BUILD @@ -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", diff --git a/pkg/kubelet/kuberuntime/kuberuntime_manager.go b/pkg/kubelet/kuberuntime/kuberuntime_manager.go index e48ae3f667b..7bc8e7799e6 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_manager.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_manager.go @@ -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, From ab04386e8f7b12888ea37c5793de4218be6cb7e5 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:06 -0500 Subject: [PATCH 4/9] feature gates: add KubeletCredentialProviders feature gate Signed-off-by: Andrew Sy Kim --- pkg/features/kube_features.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 549fb608a69..7e8d8b4a298 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -668,6 +668,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() { @@ -769,6 +775,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: From 2d0dd26252a91d8eccf460c41d6eff3f0462f2fa Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:06 -0500 Subject: [PATCH 5/9] kubelet: add initial credentialprovider v1alpha1 APIs Signed-off-by: Andrew Sy Kim --- pkg/kubelet/apis/config/types.go | 3 +- staging/src/k8s.io/kubelet/BUILD | 1 + .../k8s.io/kubelet/config/v1alpha1/types.go | 3 +- .../kubelet/pkg/apis/credentialprovider/BUILD | 37 +++++ .../pkg/apis/credentialprovider/OWNERS | 10 ++ .../pkg/apis/credentialprovider/doc.go | 20 +++ .../pkg/apis/credentialprovider/install/BUILD | 29 ++++ .../credentialprovider/install/install.go | 31 ++++ .../pkg/apis/credentialprovider/register.go | 41 ++++++ .../pkg/apis/credentialprovider/types.go | 117 +++++++++++++++ .../apis/credentialprovider/v1alpha1/BUILD | 37 +++++ .../apis/credentialprovider/v1alpha1/doc.go | 22 +++ .../credentialprovider/v1alpha1/register.go | 46 ++++++ .../apis/credentialprovider/v1alpha1/types.go | 117 +++++++++++++++ .../v1alpha1/zz_generated.conversion.go | 136 ++++++++++++++++++ .../v1alpha1/zz_generated.deepcopy.go | 104 ++++++++++++++ .../v1alpha1/zz_generated.defaults.go | 32 +++++ .../zz_generated.deepcopy.go | 104 ++++++++++++++ vendor/modules.txt | 3 + 19 files changed, 891 insertions(+), 2 deletions(-) create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/BUILD create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/OWNERS create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/doc.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/BUILD create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/install.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/register.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/types.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/BUILD create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/doc.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/register.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/types.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.conversion.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.deepcopy.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.defaults.go create mode 100644 staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/zz_generated.deepcopy.go diff --git a/pkg/kubelet/apis/config/types.go b/pkg/kubelet/apis/config/types.go index c7e5057257e..96481ad9f13 100644 --- a/pkg/kubelet/apis/config/types.go +++ b/pkg/kubelet/apis/config/types.go @@ -495,7 +495,8 @@ type CredentialProvider struct { DefaultCacheDuration *metav1.Duration // Required input version of the exec CredentialProviderRequest. The returned CredentialProviderResponse - // MUST use the same encoding version as the input. + // 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. diff --git a/staging/src/k8s.io/kubelet/BUILD b/staging/src/k8s.io/kubelet/BUILD index e84c3d6bac7..76a7c6f2fb3 100644 --- a/staging/src/k8s.io/kubelet/BUILD +++ b/staging/src/k8s.io/kubelet/BUILD @@ -11,6 +11,7 @@ filegroup( ":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", diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/types.go b/staging/src/k8s.io/kubelet/config/v1alpha1/types.go index d9b2f67efc1..e869bc18244 100644 --- a/staging/src/k8s.io/kubelet/config/v1alpha1/types.go +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/types.go @@ -74,7 +74,8 @@ type CredentialProvider struct { DefaultCacheDuration *metav1.Duration `json:"defaultCacheDuration"` // Required input version of the exec CredentialProviderRequest. The returned CredentialProviderResponse - // MUST use the same encoding version as the input. + // 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. diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/BUILD b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/BUILD new file mode 100644 index 00000000000..b59f2a3ab23 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/BUILD @@ -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"], +) diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/OWNERS b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/OWNERS new file mode 100644 index 00000000000..b74b7dda04a --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/OWNERS @@ -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 diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/doc.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/doc.go new file mode 100644 index 00000000000..181583089c8 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/doc.go @@ -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" diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/BUILD b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/BUILD new file mode 100644 index 00000000000..a85ef4812b9 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/BUILD @@ -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"], +) diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/install.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/install.go new file mode 100644 index 00000000000..df5225a870b --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/install/install.go @@ -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)) +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/register.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/register.go new file mode 100644 index 00000000000..9319c21fe67 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/register.go @@ -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 +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/types.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/types.go new file mode 100644 index 00000000000..f8e84ae6e70 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/types.go @@ -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 +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/BUILD b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/BUILD new file mode 100644 index 00000000000..d51534dfd39 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/BUILD @@ -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"], +) diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/doc.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/doc.go new file mode 100644 index 00000000000..6c0f42a0d70 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/doc.go @@ -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" diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/register.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/register.go new file mode 100644 index 00000000000..ac0984edbb0 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/register.go @@ -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 +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/types.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/types.go new file mode 100644 index 00000000000..ddc309c53f3 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/types.go @@ -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"` +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.conversion.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..a51bdd5a955 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.conversion.go @@ -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) +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..0a0e3acfefe --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.deepcopy.go @@ -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 +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.defaults.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.defaults.go new file mode 100644 index 00000000000..dd621a3acda --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1/zz_generated.defaults.go @@ -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 +} diff --git a/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/zz_generated.deepcopy.go b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/zz_generated.deepcopy.go new file mode 100644 index 00000000000..4b7b7e65077 --- /dev/null +++ b/staging/src/k8s.io/kubelet/pkg/apis/credentialprovider/zz_generated.deepcopy.go @@ -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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4cd47a1552e..49f5be7f3d3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2428,6 +2428,9 @@ k8s.io/kubectl/pkg/validation # 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 From 5344afd4fbfc3cb2ed6785d89b6f760886f100b5 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:07 -0500 Subject: [PATCH 6/9] pkg/credentialprovider: add initial exec-based credential provider plugin Signed-off-by: Andrew Sy Kim --- pkg/credentialprovider/BUILD | 1 + pkg/credentialprovider/plugin/BUILD | 58 +++ pkg/credentialprovider/plugin/config.go | 128 +++++ pkg/credentialprovider/plugin/config_test.go | 435 ++++++++++++++++ pkg/credentialprovider/plugin/plugin.go | 420 +++++++++++++++ pkg/credentialprovider/plugin/plugin_test.go | 505 +++++++++++++++++++ 6 files changed, 1547 insertions(+) create mode 100644 pkg/credentialprovider/plugin/BUILD create mode 100644 pkg/credentialprovider/plugin/config.go create mode 100644 pkg/credentialprovider/plugin/config_test.go create mode 100644 pkg/credentialprovider/plugin/plugin.go create mode 100644 pkg/credentialprovider/plugin/plugin_test.go diff --git a/pkg/credentialprovider/BUILD b/pkg/credentialprovider/BUILD index 4485ef3b127..461f5850b07 100644 --- a/pkg/credentialprovider/BUILD +++ b/pkg/credentialprovider/BUILD @@ -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"], diff --git a/pkg/credentialprovider/plugin/BUILD b/pkg/credentialprovider/plugin/BUILD new file mode 100644 index 00000000000..6362b35b6e8 --- /dev/null +++ b/pkg/credentialprovider/plugin/BUILD @@ -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", + ], +) diff --git a/pkg/credentialprovider/plugin/config.go b/pkg/credentialprovider/plugin/config.go new file mode 100644 index 00000000000..3857853de5b --- /dev/null +++ b/pkg/credentialprovider/plugin/config.go @@ -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 +} diff --git a/pkg/credentialprovider/plugin/config_test.go b/pkg/credentialprovider/plugin/config_test.go new file mode 100644 index 00000000000..c8ba3a04e6b --- /dev/null +++ b/pkg/credentialprovider/plugin/config_test.go @@ -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()) + + } + }) + } +} diff --git a/pkg/credentialprovider/plugin/plugin.go b/pkg/credentialprovider/plugin/plugin.go new file mode 100644 index 00000000000..6b1c7463df5 --- /dev/null +++ b/pkg/credentialprovider/plugin/plugin.go @@ -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 args[0] args[1] << Date: Tue, 10 Nov 2020 13:44:07 -0500 Subject: [PATCH 7/9] pkg/credentialprovider: export URL parsing and matching helper functions Signed-off-by: Andrew Sy Kim --- pkg/credentialprovider/keyring.go | 27 +++++++++++++------------- pkg/credentialprovider/keyring_test.go | 4 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pkg/credentialprovider/keyring.go b/pkg/credentialprovider/keyring.go index 494770c0e2f..5ce467f9a8b 100644 --- a/pkg/credentialprovider/keyring.go +++ b/pkg/credentialprovider/keyring.go @@ -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]...) } } diff --git a/pkg/credentialprovider/keyring_test.go b/pkg/credentialprovider/keyring_test.go index 8b5893a201d..3dd7bc2410d 100644 --- a/pkg/credentialprovider/keyring_test.go +++ b/pkg/credentialprovider/keyring_test.go @@ -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) From 91aae6ea48ed18ec649247d71e50e04e6c0306ba Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:07 -0500 Subject: [PATCH 8/9] hack/.golint: ignore golint for new kubelet and credentialprovider APIs package Signed-off-by: Andrew Sy Kim --- hack/.golint_failures | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hack/.golint_failures b/hack/.golint_failures index 85d4af8d0ac..a6fd6043f96 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -87,6 +87,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 @@ -454,6 +455,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 From f3192c31491228926f3dd5c319fc70e9c42e6aa6 Mon Sep 17 00:00:00 2001 From: Andrew Sy Kim Date: Tue, 10 Nov 2020 13:44:07 -0500 Subject: [PATCH 9/9] import restrictions: allow k8s.io/kubelet to import credentialprovider apis Signed-off-by: Andrew Sy Kim --- staging/publishing/import-restrictions.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/staging/publishing/import-restrictions.yaml b/staging/publishing/import-restrictions.yaml index 29be060cb38..e3d35afd30f 100644 --- a/staging/publishing/import-restrictions.yaml +++ b/staging/publishing/import-restrictions.yaml @@ -210,6 +210,7 @@ - k8s.io/apimachinery - k8s.io/klog - k8s.io/component-base + - k8s.io/kubelet - baseImportPath: "./vendor/k8s.io/cluster-bootstrap/" allowedImports: