mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-10-30 17:58:14 +00:00
857 lines
30 KiB
Go
857 lines
30 KiB
Go
/*
|
|
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 (
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/errors"
|
|
utiltesting "k8s.io/client-go/util/testing"
|
|
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
|
"k8s.io/utils/ptr"
|
|
)
|
|
|
|
func Test_readCredentialProviderConfigFile(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
configData string
|
|
config *kubeletconfig.CredentialProviderConfig
|
|
expectErr string
|
|
}{
|
|
{
|
|
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: "v1beta1 config with multiple providers",
|
|
configData: `---
|
|
kind: CredentialProviderConfig
|
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
|
providers:
|
|
- name: test1
|
|
matchImages:
|
|
- "registry.io/one"
|
|
defaultCacheDuration: 10m
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1beta1
|
|
- name: test2
|
|
matchImages:
|
|
- "registry.io/two"
|
|
defaultCacheDuration: 10m
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1beta1
|
|
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/v1beta1",
|
|
},
|
|
{
|
|
Name: "test2",
|
|
MatchImages: []string{"registry.io/two"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1beta1",
|
|
Args: []string{"--v=5"},
|
|
Env: []kubeletconfig.ExecEnvVar{
|
|
{
|
|
Name: "FOO",
|
|
Value: "BAR",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "v1 config with multiple providers",
|
|
configData: `---
|
|
kind: CredentialProviderConfig
|
|
apiVersion: kubelet.config.k8s.io/v1
|
|
providers:
|
|
- name: test1
|
|
matchImages:
|
|
- "registry.io/one"
|
|
defaultCacheDuration: 10m
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1
|
|
- name: test2
|
|
matchImages:
|
|
- "registry.io/two"
|
|
defaultCacheDuration: 10m
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1
|
|
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/v1",
|
|
},
|
|
{
|
|
Name: "test2",
|
|
MatchImages: []string{"registry.io/two"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: 10 * time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
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: `no kind "WrongKind" is registered for version "kubelet.config.k8s.io/v1alpha1"`,
|
|
},
|
|
{
|
|
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: `no kind "CredentialProviderConfig" is registered for version "foobar/v1alpha1`,
|
|
},
|
|
{
|
|
name: "config with invalid typo",
|
|
configData: `---
|
|
kind: CredentialProviderConfig
|
|
apiVersion: kubelet.config.k8s.io/v1
|
|
providers:
|
|
- name: test
|
|
matchImages:
|
|
- "registry.io/foobar"
|
|
defaultCacheDuration: 10m
|
|
unknownField: should not be here # this field should not be here
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
|
|
args:
|
|
- --v=5
|
|
env:
|
|
- name: FOO
|
|
value: BAR`,
|
|
config: nil,
|
|
expectErr: `strict decoding error: unknown field "providers[0].unknownField"`,
|
|
},
|
|
{
|
|
name: "v1alpha1 config with token attributes should fail",
|
|
configData: `---
|
|
kind: CredentialProviderConfig
|
|
apiVersion: kubelet.config.k8s.io/v1alpha1
|
|
providers:
|
|
- name: test
|
|
matchImages:
|
|
- "registry.io/foobar"
|
|
defaultCacheDuration: 10m
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
|
|
tokenAttributes:
|
|
serviceAccountTokenAudience: audience
|
|
args:
|
|
- --v=5
|
|
env:
|
|
- name: FOO
|
|
value: BAR`,
|
|
config: nil,
|
|
expectErr: `strict decoding error: unknown field "providers[0].tokenAttributes"`,
|
|
},
|
|
{
|
|
name: "v1beta1 config with token attributes should fail",
|
|
configData: `---
|
|
kind: CredentialProviderConfig
|
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
|
providers:
|
|
- name: test
|
|
matchImages:
|
|
- "registry.io/foobar"
|
|
defaultCacheDuration: 10m
|
|
apiVersion: credentialprovider.kubelet.k8s.io/v1beta1
|
|
tokenAttributes:
|
|
serviceAccountTokenAudience: audience
|
|
args:
|
|
- --v=5
|
|
env:
|
|
- name: FOO
|
|
value: BAR`,
|
|
config: nil,
|
|
expectErr: `strict decoding error: unknown field "providers[0].tokenAttributes"`,
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
file, err := os.CreateTemp("", "config.yaml")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer utiltesting.CloseAndRemove(t, file)
|
|
|
|
if _, err = file.WriteString(testcase.configData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
authConfig, err := readCredentialProviderConfigFile(file.Name())
|
|
if err != nil {
|
|
if len(testcase.expectErr) == 0 {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(err.Error(), testcase.expectErr) {
|
|
t.Fatalf("expected error %q but got %q", testcase.expectErr, err.Error())
|
|
}
|
|
} else if len(testcase.expectErr) > 0 {
|
|
t.Fatalf("expected error %q but got none", testcase.expectErr)
|
|
}
|
|
|
|
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
|
|
saTokenForCredentialProviders bool
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "no providers provided",
|
|
config: &kubeletconfig.CredentialProviderConfig{},
|
|
expectErr: `providers: Required value: at least 1 item in plugins is required`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.matchImages: Required value: at least 1 item in matchImages is required`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.defaultCacheDuration: Required value: defaultCacheDuration is required`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.name: Invalid value: "foo/../bar": provider name cannot contain '/'`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.name: Invalid value: ".": provider name cannot be '.'`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.name: Invalid value: "..": provider name cannot be '..'`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.name: Invalid value: "foo bar": provider name cannot contain spaces`,
|
|
},
|
|
{
|
|
name: "duplicate names",
|
|
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",
|
|
},
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"bar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1alpha1",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.name: Duplicate value: "foobar"`,
|
|
},
|
|
{
|
|
name: "no apiVersion",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "",
|
|
},
|
|
},
|
|
},
|
|
expectErr: "providers.apiVersion: Required value: apiVersion is required",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.apiVersion: Unsupported value: "credentialprovider.kubelet.k8s.io/v1alpha0": supported values: "credentialprovider.kubelet.k8s.io/v1", "credentialprovider.kubelet.k8s.io/v1alpha1", "credentialprovider.kubelet.k8s.io/v1beta1"`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: "providers.defaultCacheDuration: Invalid value: -1m0s: defaultCacheDuration must be greater than or equal to 0",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.matchImages: Invalid value: "%invalid%": match image is invalid: parse "https://%invalid%": invalid URL escape "%in"`,
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "token attributes set without KubeletServiceAccountTokenForCredentialProviders feature gate enabled",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectErr: `providers.tokenAttributes: Forbidden: tokenAttributes is not supported when KubeletServiceAccountTokenForCredentialProviders feature gate is disabled`,
|
|
},
|
|
{
|
|
name: "token attributes not nil but empty ServiceAccountTokenAudience",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
RequiredServiceAccountAnnotationKeys: []string{"prefix.io/annotation-1", "prefix.io/annotation-2"},
|
|
RequireServiceAccount: ptr.To(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.serviceAccountTokenAudience: Required value: serviceAccountTokenAudience is required`,
|
|
},
|
|
{
|
|
name: "token attributes not nil but empty ServiceAccountTokenRequired",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequiredServiceAccountAnnotationKeys: []string{"prefix.io/annotation-1", "prefix.io/annotation-2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.requireServiceAccount: Required value: requireServiceAccount is required`,
|
|
},
|
|
{
|
|
name: "required service account annotation keys not qualified name (same validation as metav1.ObjectMeta)",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
RequiredServiceAccountAnnotationKeys: []string{"cantendwithadash-", "now-with-dashes/simple"}, // first key is invalid
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.requiredServiceAccountAnnotationKeys: Invalid value: "cantendwithadash-": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`,
|
|
},
|
|
{
|
|
name: "optional service account annotation keys not qualified name (same validation as metav1.ObjectMeta)",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
OptionalServiceAccountAnnotationKeys: []string{"cantendwithadash-", "now-with-dashes/simple"}, // first key is invalid
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.optionalServiceAccountAnnotationKeys: Invalid value: "cantendwithadash-": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`,
|
|
},
|
|
{
|
|
name: "duplicate required service account annotation keys",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
RequiredServiceAccountAnnotationKeys: []string{"now-with-dashes/simple", "now-with-dashes/simple"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.requiredServiceAccountAnnotationKeys: Duplicate value: "now-with-dashes/simple"`,
|
|
},
|
|
{
|
|
name: "duplicate optional service account annotation keys",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
OptionalServiceAccountAnnotationKeys: []string{"now-with-dashes/simple", "now-with-dashes/simple"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.optionalServiceAccountAnnotationKeys: Duplicate value: "now-with-dashes/simple"`,
|
|
},
|
|
{
|
|
name: "annotation key in required and optional keys",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
RequiredServiceAccountAnnotationKeys: []string{"now-with-dashes/simple-1", "now-with-dashes/simple-2"},
|
|
OptionalServiceAccountAnnotationKeys: []string{"now-with-dashes/simple-2", "now-with-dashes/simple-3"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes: Invalid value: []string{"now-with-dashes/simple-2"}: annotation keys cannot be both required and optional`,
|
|
},
|
|
{
|
|
name: "required annotation keys set when requireServiceAccount is false",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(false),
|
|
RequiredServiceAccountAnnotationKeys: []string{"now-with-dashes/simple-1", "now-with-dashes/simple-2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes.requiredServiceAccountAnnotationKeys: Forbidden: requireServiceAccount cannot be false when requiredServiceAccountAnnotationKeys is set`,
|
|
},
|
|
{
|
|
name: "valid config with KubeletServiceAccountTokenForCredentialProviders feature gate enabled",
|
|
config: &kubeletconfig.CredentialProviderConfig{
|
|
Providers: []kubeletconfig.CredentialProvider{
|
|
{
|
|
Name: "foobar",
|
|
MatchImages: []string{"foobar.registry.io"},
|
|
DefaultCacheDuration: &metav1.Duration{Duration: time.Minute},
|
|
APIVersion: "credentialprovider.kubelet.k8s.io/v1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
RequiredServiceAccountAnnotationKeys: []string{"now-with-dashes/simple-1", "now-with-dashes/simple-2"},
|
|
OptionalServiceAccountAnnotationKeys: []string{"now-with-dashes/simple-3"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
},
|
|
{
|
|
name: "tokenAttributes set with credentialprovider.kubelet.k8s.io/v1alpha1 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/v1alpha1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
RequiredServiceAccountAnnotationKeys: []string{"now-with-dashes/simple"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes: Forbidden: tokenAttributes is only supported for credentialprovider.kubelet.k8s.io/v1 API version`,
|
|
},
|
|
{
|
|
name: "tokenAttributes set with credentialprovider.kubelet.k8s.io/v1beta1 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/v1beta1",
|
|
TokenAttributes: &kubeletconfig.ServiceAccountTokenAttributes{
|
|
ServiceAccountTokenAudience: "audience",
|
|
RequireServiceAccount: ptr.To(true),
|
|
RequiredServiceAccountAnnotationKeys: []string{"now-with-dashes/simple"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
saTokenForCredentialProviders: true,
|
|
expectErr: `providers.tokenAttributes: Forbidden: tokenAttributes is only supported for credentialprovider.kubelet.k8s.io/v1 API version`,
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
errs := validateCredentialProviderConfig(testcase.config, testcase.saTokenForCredentialProviders).ToAggregate()
|
|
if d := cmp.Diff(testcase.expectErr, errString(errs)); d != "" {
|
|
t.Fatalf("CredentialProviderConfig validation mismatch (-want +got):\n%s", d)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func errString(errs errors.Aggregate) string {
|
|
if errs != nil {
|
|
return errs.Error()
|
|
}
|
|
return ""
|
|
}
|