1
16
17 package plugin
18
19 import (
20 "fmt"
21 "strings"
22
23 "os"
24
25 "k8s.io/apimachinery/pkg/util/validation/field"
26 "k8s.io/kubernetes/pkg/credentialprovider"
27 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
28 )
29
30
31
32 func readCredentialProviderConfigFile(configPath string) (*kubeletconfig.CredentialProviderConfig, error) {
33 if configPath == "" {
34 return nil, fmt.Errorf("credential provider config path is empty")
35 }
36
37 data, err := os.ReadFile(configPath)
38 if err != nil {
39 return nil, fmt.Errorf("unable to read external registry credential provider configuration from %q: %w", configPath, err)
40 }
41
42 config, err := decode(data)
43 if err != nil {
44 return nil, fmt.Errorf("error decoding config %s: %w", configPath, err)
45 }
46
47 return config, nil
48 }
49
50
51 func decode(data []byte) (*kubeletconfig.CredentialProviderConfig, error) {
52 obj, gvk, err := codecs.UniversalDecoder().Decode(data, nil, nil)
53 if err != nil {
54 return nil, err
55 }
56
57 if gvk.Kind != "CredentialProviderConfig" {
58 return nil, fmt.Errorf("failed to decode %q (wrong Kind)", gvk.Kind)
59 }
60
61 if gvk.Group != kubeletconfig.GroupName {
62 return nil, fmt.Errorf("failed to decode CredentialProviderConfig, unexpected Group: %s", gvk.Group)
63 }
64
65 if internalConfig, ok := obj.(*kubeletconfig.CredentialProviderConfig); ok {
66 return internalConfig, nil
67 }
68
69 return nil, fmt.Errorf("unable to convert %T to *CredentialProviderConfig", obj)
70 }
71
72
73 func validateCredentialProviderConfig(config *kubeletconfig.CredentialProviderConfig) field.ErrorList {
74 allErrs := field.ErrorList{}
75
76 if len(config.Providers) == 0 {
77 allErrs = append(allErrs, field.Required(field.NewPath("providers"), "at least 1 item in plugins is required"))
78 }
79
80 fieldPath := field.NewPath("providers")
81 for _, provider := range config.Providers {
82 if strings.Contains(provider.Name, "/") {
83 allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot contain '/'"))
84 }
85
86 if strings.Contains(provider.Name, " ") {
87 allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot contain spaces"))
88 }
89
90 if provider.Name == "." {
91 allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot be '.'"))
92 }
93
94 if provider.Name == ".." {
95 allErrs = append(allErrs, field.Invalid(fieldPath.Child("name"), provider.Name, "provider name cannot be '..'"))
96 }
97
98 if provider.APIVersion == "" {
99 allErrs = append(allErrs, field.Required(fieldPath.Child("apiVersion"), "apiVersion is required"))
100 } else if _, ok := apiVersions[provider.APIVersion]; !ok {
101 validAPIVersions := []string{}
102 for apiVersion := range apiVersions {
103 validAPIVersions = append(validAPIVersions, apiVersion)
104 }
105
106 allErrs = append(allErrs, field.NotSupported(fieldPath.Child("apiVersion"), provider.APIVersion, validAPIVersions))
107 }
108
109 if len(provider.MatchImages) == 0 {
110 allErrs = append(allErrs, field.Required(fieldPath.Child("matchImages"), "at least 1 item in matchImages is required"))
111 }
112
113 for _, matchImage := range provider.MatchImages {
114 if _, err := credentialprovider.ParseSchemelessURL(matchImage); err != nil {
115 allErrs = append(allErrs, field.Invalid(fieldPath.Child("matchImages"), matchImage, fmt.Sprintf("match image is invalid: %s", err.Error())))
116 }
117 }
118
119 if provider.DefaultCacheDuration == nil {
120 allErrs = append(allErrs, field.Required(fieldPath.Child("defaultCacheDuration"), "defaultCacheDuration is required"))
121 }
122
123 if provider.DefaultCacheDuration != nil && provider.DefaultCacheDuration.Duration < 0 {
124 allErrs = append(allErrs, field.Invalid(fieldPath.Child("defaultCacheDuration"), provider.DefaultCacheDuration.Duration, "defaultCacheDuration must be greater than or equal to 0"))
125 }
126 }
127
128 return allErrs
129 }
130
View as plain text