1
16
17 package clientcmd
18
19 import (
20 "errors"
21 "fmt"
22 "os"
23 "reflect"
24 "strings"
25
26 utilerrors "k8s.io/apimachinery/pkg/util/errors"
27 "k8s.io/apimachinery/pkg/util/validation"
28 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
29 )
30
31 var (
32 ErrNoContext = errors.New("no context chosen")
33 ErrEmptyConfig = NewEmptyConfigError("no configuration has been provided, try setting KUBERNETES_MASTER environment variable")
34
35 ErrEmptyCluster = errors.New("cluster has no server defined")
36 )
37
38
39 func NewEmptyConfigError(message string) error {
40 return &errEmptyConfig{message}
41 }
42
43 type errEmptyConfig struct {
44 message string
45 }
46
47 func (e *errEmptyConfig) Error() string {
48 return e.message
49 }
50
51 type errContextNotFound struct {
52 ContextName string
53 }
54
55 func (e *errContextNotFound) Error() string {
56 return fmt.Sprintf("context was not found for specified context: %v", e.ContextName)
57 }
58
59
60
61 func IsContextNotFound(err error) bool {
62 if err == nil {
63 return false
64 }
65 if _, ok := err.(*errContextNotFound); ok || err == ErrNoContext {
66 return true
67 }
68 return strings.Contains(err.Error(), "context was not found for specified context")
69 }
70
71
72
73 func IsEmptyConfig(err error) bool {
74 switch t := err.(type) {
75 case errConfigurationInvalid:
76 if len(t) != 1 {
77 return false
78 }
79 _, ok := t[0].(*errEmptyConfig)
80 return ok
81 }
82 _, ok := err.(*errEmptyConfig)
83 return ok
84 }
85
86
87 type errConfigurationInvalid []error
88
89
90 var _ error = errConfigurationInvalid{}
91 var _ utilerrors.Aggregate = errConfigurationInvalid{}
92
93 func newErrConfigurationInvalid(errs []error) error {
94 switch len(errs) {
95 case 0:
96 return nil
97 default:
98 return errConfigurationInvalid(errs)
99 }
100 }
101
102
103 func (e errConfigurationInvalid) Error() string {
104 return fmt.Sprintf("invalid configuration: %v", utilerrors.NewAggregate(e).Error())
105 }
106
107
108 func (e errConfigurationInvalid) Errors() []error {
109 return e
110 }
111
112
113 func (e errConfigurationInvalid) Is(target error) bool {
114 return e.visit(func(err error) bool {
115 return errors.Is(err, target)
116 })
117 }
118
119 func (e errConfigurationInvalid) visit(f func(err error) bool) bool {
120 for _, err := range e {
121 switch err := err.(type) {
122 case errConfigurationInvalid:
123 if match := err.visit(f); match {
124 return match
125 }
126 case utilerrors.Aggregate:
127 for _, nestedErr := range err.Errors() {
128 if match := f(nestedErr); match {
129 return match
130 }
131 }
132 default:
133 if match := f(err); match {
134 return match
135 }
136 }
137 }
138
139 return false
140 }
141
142
143 func IsConfigurationInvalid(err error) bool {
144 switch err.(type) {
145 case *errContextNotFound, errConfigurationInvalid:
146 return true
147 }
148 return IsContextNotFound(err)
149 }
150
151
152 func Validate(config clientcmdapi.Config) error {
153 validationErrors := make([]error, 0)
154
155 if clientcmdapi.IsConfigEmpty(&config) {
156 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
157 }
158
159 if len(config.CurrentContext) != 0 {
160 if _, exists := config.Contexts[config.CurrentContext]; !exists {
161 validationErrors = append(validationErrors, &errContextNotFound{config.CurrentContext})
162 }
163 }
164
165 for contextName, context := range config.Contexts {
166 validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
167 }
168
169 for authInfoName, authInfo := range config.AuthInfos {
170 validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
171 }
172
173 for clusterName, clusterInfo := range config.Clusters {
174 validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
175 }
176
177 return newErrConfigurationInvalid(validationErrors)
178 }
179
180
181
182 func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
183 validationErrors := make([]error, 0)
184
185 if clientcmdapi.IsConfigEmpty(&config) {
186 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
187 }
188
189 var contextName string
190 if len(passedContextName) != 0 {
191 contextName = passedContextName
192 } else {
193 contextName = config.CurrentContext
194 }
195
196 if len(contextName) == 0 {
197 return ErrNoContext
198 }
199
200 context, exists := config.Contexts[contextName]
201 if !exists {
202 validationErrors = append(validationErrors, &errContextNotFound{contextName})
203 }
204
205 if exists {
206 validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
207
208
209 authInfo := config.AuthInfos[context.AuthInfo]
210 if authInfo == nil {
211 authInfo = &clientcmdapi.AuthInfo{}
212 }
213 validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, *authInfo)...)
214
215 cluster := config.Clusters[context.Cluster]
216 if cluster == nil {
217 cluster = &clientcmdapi.Cluster{}
218 }
219 validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, *cluster)...)
220 }
221
222 return newErrConfigurationInvalid(validationErrors)
223 }
224
225
226 func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) []error {
227 validationErrors := make([]error, 0)
228
229 emptyCluster := clientcmdapi.NewCluster()
230 if reflect.DeepEqual(*emptyCluster, clusterInfo) {
231 return []error{ErrEmptyCluster}
232 }
233
234 if len(clusterInfo.Server) == 0 {
235 if len(clusterName) == 0 {
236 validationErrors = append(validationErrors, fmt.Errorf("default cluster has no server defined"))
237 } else {
238 validationErrors = append(validationErrors, fmt.Errorf("no server found for cluster %q", clusterName))
239 }
240 }
241 if proxyURL := clusterInfo.ProxyURL; proxyURL != "" {
242 if _, err := parseProxyURL(proxyURL); err != nil {
243 validationErrors = append(validationErrors, fmt.Errorf("invalid 'proxy-url' %q for cluster %q: %w", proxyURL, clusterName, err))
244 }
245 }
246
247 if len(clusterInfo.CertificateAuthority) != 0 && len(clusterInfo.CertificateAuthorityData) != 0 {
248 validationErrors = append(validationErrors, fmt.Errorf("certificate-authority-data and certificate-authority are both specified for %v. certificate-authority-data will override.", clusterName))
249 }
250 if len(clusterInfo.CertificateAuthority) != 0 {
251 clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
252 if err != nil {
253 validationErrors = append(validationErrors, fmt.Errorf("unable to read certificate-authority %v for %v due to %w", clusterInfo.CertificateAuthority, clusterName, err))
254 } else {
255 defer clientCertCA.Close()
256 }
257 }
258
259 return validationErrors
260 }
261
262
263 func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []error {
264 validationErrors := make([]error, 0)
265
266 usingAuthPath := false
267 methods := make([]string, 0, 3)
268 if len(authInfo.Token) != 0 {
269 methods = append(methods, "token")
270 }
271 if len(authInfo.Username) != 0 || len(authInfo.Password) != 0 {
272 methods = append(methods, "basicAuth")
273 }
274
275 if len(authInfo.ClientCertificate) != 0 || len(authInfo.ClientCertificateData) != 0 {
276
277 if len(authInfo.ClientCertificate) != 0 && len(authInfo.ClientCertificateData) != 0 {
278 validationErrors = append(validationErrors, fmt.Errorf("client-cert-data and client-cert are both specified for %v. client-cert-data will override.", authInfoName))
279 }
280
281 if len(authInfo.ClientKey) != 0 && len(authInfo.ClientKeyData) != 0 {
282 validationErrors = append(validationErrors, fmt.Errorf("client-key-data and client-key are both specified for %v; client-key-data will override", authInfoName))
283 }
284
285 if len(authInfo.ClientKey) == 0 && len(authInfo.ClientKeyData) == 0 {
286 validationErrors = append(validationErrors, fmt.Errorf("client-key-data or client-key must be specified for %v to use the clientCert authentication method.", authInfoName))
287 }
288
289 if len(authInfo.ClientCertificate) != 0 {
290 clientCertFile, err := os.Open(authInfo.ClientCertificate)
291 if err != nil {
292 validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %w", authInfo.ClientCertificate, authInfoName, err))
293 } else {
294 defer clientCertFile.Close()
295 }
296 }
297 if len(authInfo.ClientKey) != 0 {
298 clientKeyFile, err := os.Open(authInfo.ClientKey)
299 if err != nil {
300 validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %w", authInfo.ClientKey, authInfoName, err))
301 } else {
302 defer clientKeyFile.Close()
303 }
304 }
305 }
306
307 if authInfo.Exec != nil {
308 if authInfo.AuthProvider != nil {
309 validationErrors = append(validationErrors, fmt.Errorf("authProvider cannot be provided in combination with an exec plugin for %s", authInfoName))
310 }
311 if len(authInfo.Exec.Command) == 0 {
312 validationErrors = append(validationErrors, fmt.Errorf("command must be specified for %v to use exec authentication plugin", authInfoName))
313 }
314 if len(authInfo.Exec.APIVersion) == 0 {
315 validationErrors = append(validationErrors, fmt.Errorf("apiVersion must be specified for %v to use exec authentication plugin", authInfoName))
316 }
317 for _, v := range authInfo.Exec.Env {
318 if len(v.Name) == 0 {
319 validationErrors = append(validationErrors, fmt.Errorf("env variable name must be specified for %v to use exec authentication plugin", authInfoName))
320 }
321 }
322 switch authInfo.Exec.InteractiveMode {
323 case "":
324 validationErrors = append(validationErrors, fmt.Errorf("interactiveMode must be specified for %v to use exec authentication plugin", authInfoName))
325 case clientcmdapi.NeverExecInteractiveMode, clientcmdapi.IfAvailableExecInteractiveMode, clientcmdapi.AlwaysExecInteractiveMode:
326
327 default:
328 validationErrors = append(validationErrors, fmt.Errorf("invalid interactiveMode for %v: %q", authInfoName, authInfo.Exec.InteractiveMode))
329 }
330 }
331
332
333 if (len(methods) > 1) && (!usingAuthPath) {
334 validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))
335 }
336
337
338 if (len(authInfo.ImpersonateUID) > 0 || len(authInfo.ImpersonateGroups) > 0 || len(authInfo.ImpersonateUserExtra) > 0) && (len(authInfo.Impersonate) == 0) {
339 validationErrors = append(validationErrors, fmt.Errorf("requesting uid, groups or user-extra for %v without impersonating a user", authInfoName))
340 }
341 return validationErrors
342 }
343
344
345 func validateContext(contextName string, context clientcmdapi.Context, config clientcmdapi.Config) []error {
346 validationErrors := make([]error, 0)
347
348 if len(contextName) == 0 {
349 validationErrors = append(validationErrors, fmt.Errorf("empty context name for %#v is not allowed", context))
350 }
351
352 if len(context.AuthInfo) == 0 {
353 validationErrors = append(validationErrors, fmt.Errorf("user was not specified for context %q", contextName))
354 } else if _, exists := config.AuthInfos[context.AuthInfo]; !exists {
355 validationErrors = append(validationErrors, fmt.Errorf("user %q was not found for context %q", context.AuthInfo, contextName))
356 }
357
358 if len(context.Cluster) == 0 {
359 validationErrors = append(validationErrors, fmt.Errorf("cluster was not specified for context %q", contextName))
360 } else if _, exists := config.Clusters[context.Cluster]; !exists {
361 validationErrors = append(validationErrors, fmt.Errorf("cluster %q was not found for context %q", context.Cluster, contextName))
362 }
363
364 if len(context.Namespace) != 0 {
365 if len(validation.IsDNS1123Label(context.Namespace)) != 0 {
366 validationErrors = append(validationErrors, fmt.Errorf("namespace %q for context %q does not conform to the kubernetes DNS_LABEL rules", context.Namespace, contextName))
367 }
368 }
369
370 return validationErrors
371 }
372
View as plain text