k8s.io/apiserver@v0.31.1/pkg/apis/apiserver/validation/validation_encryption_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/apimachinery/pkg/util/validation/field"
    29  	"k8s.io/apiserver/pkg/apis/apiserver"
    30  )
    31  
    32  func TestStructure(t *testing.T) {
    33  	root := field.NewPath("resources")
    34  	firstResourcePath := root.Index(0)
    35  	cacheSize := int32(1)
    36  	testCases := []struct {
    37  		desc   string
    38  		in     *apiserver.EncryptionConfiguration
    39  		reload bool
    40  		want   field.ErrorList
    41  	}{{
    42  		desc: "nil encryption config",
    43  		in:   nil,
    44  		want: field.ErrorList{
    45  			field.Required(root, encryptionConfigNilErr),
    46  		},
    47  	}, {
    48  		desc: "empty encryption config",
    49  		in:   &apiserver.EncryptionConfiguration{},
    50  		want: field.ErrorList{
    51  			field.Required(root, fmt.Sprintf(atLeastOneRequiredErrFmt, root)),
    52  		},
    53  	}, {
    54  		desc: "no k8s resources",
    55  		in: &apiserver.EncryptionConfiguration{
    56  			Resources: []apiserver.ResourceConfiguration{{
    57  				Providers: []apiserver.ProviderConfiguration{{
    58  					AESCBC: &apiserver.AESConfiguration{
    59  						Keys: []apiserver.Key{{
    60  							Name:   "foo",
    61  							Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
    62  						}},
    63  					},
    64  				}},
    65  			}},
    66  		},
    67  		want: field.ErrorList{
    68  			field.Required(firstResourcePath.Child("resources"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("resources"))),
    69  		},
    70  	}, {
    71  		desc: "no providers",
    72  		in: &apiserver.EncryptionConfiguration{
    73  			Resources: []apiserver.ResourceConfiguration{{
    74  				Resources: []string{"secrets"},
    75  			}},
    76  		},
    77  		want: field.ErrorList{
    78  			field.Required(firstResourcePath.Child("providers"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("providers"))),
    79  		},
    80  	}, {
    81  		desc: "multiple providers",
    82  		in: &apiserver.EncryptionConfiguration{
    83  			Resources: []apiserver.ResourceConfiguration{{
    84  				Resources: []string{"secrets"},
    85  				Providers: []apiserver.ProviderConfiguration{{
    86  					AESGCM: &apiserver.AESConfiguration{
    87  						Keys: []apiserver.Key{{
    88  							Name:   "foo",
    89  							Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
    90  						}},
    91  					},
    92  					AESCBC: &apiserver.AESConfiguration{
    93  						Keys: []apiserver.Key{{
    94  							Name:   "foo",
    95  							Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
    96  						}},
    97  					},
    98  				}},
    99  			}},
   100  		},
   101  		want: field.ErrorList{
   102  			field.Invalid(
   103  				firstResourcePath.Child("providers").Index(0),
   104  				apiserver.ProviderConfiguration{
   105  					AESGCM: &apiserver.AESConfiguration{
   106  						Keys: []apiserver.Key{{
   107  							Name:   "foo",
   108  							Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
   109  						}},
   110  					},
   111  					AESCBC: &apiserver.AESConfiguration{
   112  						Keys: []apiserver.Key{{
   113  							Name:   "foo",
   114  							Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
   115  						}},
   116  					},
   117  				},
   118  				moreThanOneElementErr),
   119  		},
   120  	}, {
   121  		desc: "valid config",
   122  		in: &apiserver.EncryptionConfiguration{
   123  			Resources: []apiserver.ResourceConfiguration{{
   124  				Resources: []string{"secrets"},
   125  				Providers: []apiserver.ProviderConfiguration{{
   126  					AESGCM: &apiserver.AESConfiguration{
   127  						Keys: []apiserver.Key{{
   128  							Name:   "foo",
   129  							Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
   130  						}},
   131  					},
   132  				}},
   133  			}},
   134  		},
   135  		want: field.ErrorList{},
   136  	}, {
   137  		desc: "duplicate kms v2 config name with kms v1 config",
   138  		in: &apiserver.EncryptionConfiguration{
   139  			Resources: []apiserver.ResourceConfiguration{{
   140  				Resources: []string{"secrets"},
   141  				Providers: []apiserver.ProviderConfiguration{{
   142  					KMS: &apiserver.KMSConfiguration{
   143  						Name:       "foo",
   144  						Endpoint:   "unix:///tmp/kms-provider-1.socket",
   145  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   146  						CacheSize:  &cacheSize,
   147  						APIVersion: "v1",
   148  					},
   149  				}, {
   150  					KMS: &apiserver.KMSConfiguration{
   151  						Name:       "foo",
   152  						Endpoint:   "unix:///tmp/kms-provider-2.socket",
   153  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   154  						APIVersion: "v2",
   155  					},
   156  				}},
   157  			}},
   158  		},
   159  		want: field.ErrorList{
   160  			field.Invalid(firstResourcePath.Child("providers").Index(1).Child("kms").Child("name"),
   161  				"foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
   162  		},
   163  	}, {
   164  		desc: "duplicate kms v2 config names",
   165  		in: &apiserver.EncryptionConfiguration{
   166  			Resources: []apiserver.ResourceConfiguration{{
   167  				Resources: []string{"secrets"},
   168  				Providers: []apiserver.ProviderConfiguration{{
   169  					KMS: &apiserver.KMSConfiguration{
   170  						Name:       "foo",
   171  						Endpoint:   "unix:///tmp/kms-provider-1.socket",
   172  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   173  						APIVersion: "v2",
   174  					},
   175  				}, {
   176  					KMS: &apiserver.KMSConfiguration{
   177  						Name:       "foo",
   178  						Endpoint:   "unix:///tmp/kms-provider-2.socket",
   179  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   180  						APIVersion: "v2",
   181  					},
   182  				}},
   183  			}},
   184  		},
   185  		want: field.ErrorList{
   186  			field.Invalid(firstResourcePath.Child("providers").Index(1).Child("kms").Child("name"),
   187  				"foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
   188  		},
   189  	}, {
   190  		desc: "duplicate kms v2 config name across providers",
   191  		in: &apiserver.EncryptionConfiguration{
   192  			Resources: []apiserver.ResourceConfiguration{{
   193  				Resources: []string{"secrets"},
   194  				Providers: []apiserver.ProviderConfiguration{{
   195  					KMS: &apiserver.KMSConfiguration{
   196  						Name:       "foo",
   197  						Endpoint:   "unix:///tmp/kms-provider-1.socket",
   198  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   199  						APIVersion: "v2",
   200  					},
   201  				}},
   202  			}, {
   203  				Resources: []string{"secrets"},
   204  				Providers: []apiserver.ProviderConfiguration{{
   205  					KMS: &apiserver.KMSConfiguration{
   206  						Name:       "foo",
   207  						Endpoint:   "unix:///tmp/kms-provider-2.socket",
   208  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   209  						APIVersion: "v2",
   210  					},
   211  				}},
   212  			}},
   213  		},
   214  		want: field.ErrorList{
   215  			field.Invalid(root.Index(1).Child("providers").Index(0).Child("kms").Child("name"),
   216  				"foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
   217  		},
   218  	}, {
   219  		desc: "duplicate kms config name with v1 and v2 across providers",
   220  		in: &apiserver.EncryptionConfiguration{
   221  			Resources: []apiserver.ResourceConfiguration{{
   222  				Resources: []string{"secrets"},
   223  				Providers: []apiserver.ProviderConfiguration{{
   224  					KMS: &apiserver.KMSConfiguration{
   225  						Name:       "foo",
   226  						Endpoint:   "unix:///tmp/kms-provider-1.socket",
   227  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   228  						CacheSize:  &cacheSize,
   229  						APIVersion: "v1",
   230  					},
   231  				}},
   232  			}, {
   233  				Resources: []string{"secrets"},
   234  				Providers: []apiserver.ProviderConfiguration{{
   235  					KMS: &apiserver.KMSConfiguration{
   236  						Name:       "foo",
   237  						Endpoint:   "unix:///tmp/kms-provider-2.socket",
   238  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   239  						APIVersion: "v2",
   240  					},
   241  				}},
   242  			}},
   243  		},
   244  		want: field.ErrorList{
   245  			field.Invalid(root.Index(1).Child("providers").Index(0).Child("kms").Child("name"),
   246  				"foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
   247  		},
   248  	}, {
   249  		desc: "duplicate kms v1 config names shouldn't error",
   250  		in: &apiserver.EncryptionConfiguration{
   251  			Resources: []apiserver.ResourceConfiguration{{
   252  				Resources: []string{"secrets"},
   253  				Providers: []apiserver.ProviderConfiguration{{
   254  					KMS: &apiserver.KMSConfiguration{
   255  						Name:       "foo",
   256  						Endpoint:   "unix:///tmp/kms-provider-1.socket",
   257  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   258  						CacheSize:  &cacheSize,
   259  						APIVersion: "v1",
   260  					},
   261  				}, {
   262  					KMS: &apiserver.KMSConfiguration{
   263  						Name:       "foo",
   264  						Endpoint:   "unix:///tmp/kms-provider-2.socket",
   265  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   266  						CacheSize:  &cacheSize,
   267  						APIVersion: "v1",
   268  					},
   269  				}},
   270  			}},
   271  		},
   272  		want: field.ErrorList{},
   273  	}, {
   274  		desc: "duplicate kms v1 config names should error when reload=true",
   275  		in: &apiserver.EncryptionConfiguration{
   276  			Resources: []apiserver.ResourceConfiguration{{
   277  				Resources: []string{"secrets"},
   278  				Providers: []apiserver.ProviderConfiguration{{
   279  					KMS: &apiserver.KMSConfiguration{
   280  						Name:       "foo",
   281  						Endpoint:   "unix:///tmp/kms-provider-1.socket",
   282  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   283  						CacheSize:  &cacheSize,
   284  						APIVersion: "v1",
   285  					},
   286  				}, {
   287  					KMS: &apiserver.KMSConfiguration{
   288  						Name:       "foo",
   289  						Endpoint:   "unix:///tmp/kms-provider-2.socket",
   290  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   291  						CacheSize:  &cacheSize,
   292  						APIVersion: "v1",
   293  					},
   294  				}},
   295  			}},
   296  		},
   297  		reload: true,
   298  		want: field.ErrorList{
   299  			field.Invalid(root.Index(0).Child("providers").Index(1).Child("kms").Child("name"),
   300  				"foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
   301  		},
   302  	}, {
   303  		desc: "config should error when events.k8s.io group is used",
   304  		in: &apiserver.EncryptionConfiguration{
   305  			Resources: []apiserver.ResourceConfiguration{{
   306  				Resources: []string{
   307  					"events.events.k8s.io",
   308  				},
   309  				Providers: []apiserver.ProviderConfiguration{{
   310  					KMS: &apiserver.KMSConfiguration{
   311  						Name:       "foo",
   312  						Endpoint:   "unix:///tmp/kms-provider.socket",
   313  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   314  						CacheSize:  &cacheSize,
   315  						APIVersion: "v1",
   316  					},
   317  				}},
   318  			}},
   319  		},
   320  		reload: false,
   321  		want: field.ErrorList{
   322  			field.Invalid(
   323  				root.Index(0).Child("resources").Index(0),
   324  				"events.events.k8s.io",
   325  				eventsGroupErr,
   326  			),
   327  		},
   328  	}, {
   329  		desc: "config should error when events.k8s.io group is used later in the list",
   330  		in: &apiserver.EncryptionConfiguration{
   331  			Resources: []apiserver.ResourceConfiguration{{
   332  				Resources: []string{
   333  					"secrets",
   334  				},
   335  				Providers: []apiserver.ProviderConfiguration{{
   336  					KMS: &apiserver.KMSConfiguration{
   337  						Name:       "foo",
   338  						Endpoint:   "unix:///tmp/kms-provider.socket",
   339  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   340  						CacheSize:  &cacheSize,
   341  						APIVersion: "v1",
   342  					},
   343  				}},
   344  			}, {
   345  				Resources: []string{
   346  					"secret",
   347  					"events.events.k8s.io",
   348  				},
   349  				Providers: []apiserver.ProviderConfiguration{{
   350  					KMS: &apiserver.KMSConfiguration{
   351  						Name:       "foo",
   352  						Endpoint:   "unix:///tmp/kms-provider.socket",
   353  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   354  						CacheSize:  &cacheSize,
   355  						APIVersion: "v1",
   356  					},
   357  				}},
   358  			}},
   359  		},
   360  		reload: false,
   361  		want: field.ErrorList{
   362  			field.Invalid(
   363  				root.Index(1).Child("resources").Index(1),
   364  				"events.events.k8s.io",
   365  				eventsGroupErr,
   366  			),
   367  		},
   368  	}, {
   369  		desc: "config should error when *.events.k8s.io group is used",
   370  		in: &apiserver.EncryptionConfiguration{
   371  			Resources: []apiserver.ResourceConfiguration{{
   372  				Resources: []string{
   373  					"*.events.k8s.io",
   374  				},
   375  				Providers: []apiserver.ProviderConfiguration{{
   376  					KMS: &apiserver.KMSConfiguration{
   377  						Name:       "foo",
   378  						Endpoint:   "unix:///tmp/kms-provider.socket",
   379  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   380  						CacheSize:  &cacheSize,
   381  						APIVersion: "v1",
   382  					},
   383  				}},
   384  			}},
   385  		},
   386  		reload: false,
   387  		want: field.ErrorList{
   388  			field.Invalid(
   389  				root.Index(0).Child("resources").Index(0),
   390  				"*.events.k8s.io",
   391  				eventsGroupErr,
   392  			),
   393  		},
   394  	}, {
   395  		desc: "config should error when extensions group is used",
   396  		in: &apiserver.EncryptionConfiguration{
   397  			Resources: []apiserver.ResourceConfiguration{{
   398  				Resources: []string{
   399  					"*.extensions",
   400  				},
   401  				Providers: []apiserver.ProviderConfiguration{{
   402  					KMS: &apiserver.KMSConfiguration{
   403  						Name:       "foo",
   404  						Endpoint:   "unix:///tmp/kms-provider.socket",
   405  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   406  						CacheSize:  &cacheSize,
   407  						APIVersion: "v1",
   408  					},
   409  				}},
   410  			}},
   411  		},
   412  		reload: false,
   413  		want: field.ErrorList{
   414  			field.Invalid(
   415  				root.Index(0).Child("resources").Index(0),
   416  				"*.extensions",
   417  				extensionsGroupErr,
   418  			),
   419  		},
   420  	}, {
   421  		desc: "config should error when foo.extensions group is used",
   422  		in: &apiserver.EncryptionConfiguration{
   423  			Resources: []apiserver.ResourceConfiguration{{
   424  				Resources: []string{
   425  					"foo.extensions",
   426  				},
   427  				Providers: []apiserver.ProviderConfiguration{{
   428  					KMS: &apiserver.KMSConfiguration{
   429  						Name:       "foo",
   430  						Endpoint:   "unix:///tmp/kms-provider.socket",
   431  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   432  						CacheSize:  &cacheSize,
   433  						APIVersion: "v1",
   434  					},
   435  				}},
   436  			}},
   437  		},
   438  		reload: false,
   439  		want: field.ErrorList{
   440  			field.Invalid(
   441  				root.Index(0).Child("resources").Index(0),
   442  				"foo.extensions",
   443  				extensionsGroupErr,
   444  			),
   445  		},
   446  	}, {
   447  		desc: "config should error when '*' resource is used",
   448  		in: &apiserver.EncryptionConfiguration{
   449  			Resources: []apiserver.ResourceConfiguration{{
   450  				Resources: []string{
   451  					"*",
   452  				},
   453  				Providers: []apiserver.ProviderConfiguration{{
   454  					KMS: &apiserver.KMSConfiguration{
   455  						Name:       "foo",
   456  						Endpoint:   "unix:///tmp/kms-provider.socket",
   457  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   458  						CacheSize:  &cacheSize,
   459  						APIVersion: "v1",
   460  					},
   461  				}},
   462  			}},
   463  		},
   464  		reload: false,
   465  		want: field.ErrorList{
   466  			field.Invalid(
   467  				root.Index(0).Child("resources").Index(0),
   468  				"*",
   469  				starResourceErr,
   470  			),
   471  		},
   472  	}, {
   473  		desc: "should error when resource name has capital letters",
   474  		in: &apiserver.EncryptionConfiguration{
   475  			Resources: []apiserver.ResourceConfiguration{{
   476  				Resources: []string{
   477  					"apiServerIPInfo",
   478  				},
   479  				Providers: []apiserver.ProviderConfiguration{{
   480  					KMS: &apiserver.KMSConfiguration{
   481  						Name:       "foo",
   482  						Endpoint:   "unix:///tmp/kms-provider.socket",
   483  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   484  						CacheSize:  &cacheSize,
   485  						APIVersion: "v1",
   486  					},
   487  				}},
   488  			}},
   489  		},
   490  		reload: false,
   491  		want: field.ErrorList{
   492  			field.Invalid(
   493  				root.Index(0).Child("resources").Index(0),
   494  				"apiServerIPInfo",
   495  				resourceNameErr,
   496  			),
   497  		},
   498  	}, {
   499  		desc: "should error when resource name is apiserveripinfo",
   500  		in: &apiserver.EncryptionConfiguration{
   501  			Resources: []apiserver.ResourceConfiguration{{
   502  				Resources: []string{
   503  					"apiserveripinfo",
   504  				},
   505  				Providers: []apiserver.ProviderConfiguration{{
   506  					KMS: &apiserver.KMSConfiguration{
   507  						Name:       "foo",
   508  						Endpoint:   "unix:///tmp/kms-provider.socket",
   509  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   510  						CacheSize:  &cacheSize,
   511  						APIVersion: "v1",
   512  					},
   513  				}},
   514  			}},
   515  		},
   516  		reload: false,
   517  		want: field.ErrorList{
   518  			field.Invalid(
   519  				root.Index(0).Child("resources").Index(0),
   520  				"apiserveripinfo",
   521  				nonRESTAPIResourceErr,
   522  			),
   523  		},
   524  	}, {
   525  		desc: "should error when resource name is serviceipallocations",
   526  		in: &apiserver.EncryptionConfiguration{
   527  			Resources: []apiserver.ResourceConfiguration{{
   528  				Resources: []string{
   529  					"serviceipallocations",
   530  				},
   531  				Providers: []apiserver.ProviderConfiguration{{
   532  					KMS: &apiserver.KMSConfiguration{
   533  						Name:       "foo",
   534  						Endpoint:   "unix:///tmp/kms-provider.socket",
   535  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   536  						CacheSize:  &cacheSize,
   537  						APIVersion: "v1",
   538  					},
   539  				}},
   540  			}},
   541  		},
   542  		reload: false,
   543  		want: field.ErrorList{
   544  			field.Invalid(
   545  				root.Index(0).Child("resources").Index(0),
   546  				"serviceipallocations",
   547  				nonRESTAPIResourceErr,
   548  			),
   549  		},
   550  	}, {
   551  		desc: "should error when resource name is servicenodeportallocations",
   552  		in: &apiserver.EncryptionConfiguration{
   553  			Resources: []apiserver.ResourceConfiguration{{
   554  				Resources: []string{
   555  					"servicenodeportallocations",
   556  				},
   557  				Providers: []apiserver.ProviderConfiguration{{
   558  					KMS: &apiserver.KMSConfiguration{
   559  						Name:       "foo",
   560  						Endpoint:   "unix:///tmp/kms-provider.socket",
   561  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   562  						CacheSize:  &cacheSize,
   563  						APIVersion: "v1",
   564  					},
   565  				}},
   566  			}},
   567  		},
   568  		reload: false,
   569  		want: field.ErrorList{
   570  			field.Invalid(
   571  				root.Index(0).Child("resources").Index(0),
   572  				"servicenodeportallocations",
   573  				nonRESTAPIResourceErr,
   574  			),
   575  		},
   576  	}, {
   577  		desc: "should not error when '*.apps' and '*.' are used within the same resource list",
   578  		in: &apiserver.EncryptionConfiguration{
   579  			Resources: []apiserver.ResourceConfiguration{{
   580  				Resources: []string{
   581  					"*.apps",
   582  					"*.",
   583  				},
   584  				Providers: []apiserver.ProviderConfiguration{{
   585  					KMS: &apiserver.KMSConfiguration{
   586  						Name:       "foo",
   587  						Endpoint:   "unix:///tmp/kms-provider.socket",
   588  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   589  						CacheSize:  &cacheSize,
   590  						APIVersion: "v1",
   591  					},
   592  				}},
   593  			}},
   594  		},
   595  		reload: false,
   596  		want:   field.ErrorList{},
   597  	}, {
   598  		desc: "should error when the same resource across groups is encrypted",
   599  		in: &apiserver.EncryptionConfiguration{
   600  			Resources: []apiserver.ResourceConfiguration{{
   601  				Resources: []string{
   602  					"*.",
   603  					"foos.*",
   604  				},
   605  				Providers: []apiserver.ProviderConfiguration{{
   606  					KMS: &apiserver.KMSConfiguration{
   607  						Name:       "foo",
   608  						Endpoint:   "unix:///tmp/kms-provider.socket",
   609  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   610  						CacheSize:  &cacheSize,
   611  						APIVersion: "v1",
   612  					},
   613  				}},
   614  			}},
   615  		},
   616  		reload: false,
   617  		want: field.ErrorList{
   618  			field.Invalid(
   619  				root.Index(0).Child("resources").Index(1),
   620  				"foos.*",
   621  				resourceAcrossGroupErr,
   622  			),
   623  		},
   624  	}, {
   625  		desc: "should error when secrets are specified twice within the same resource list",
   626  		in: &apiserver.EncryptionConfiguration{
   627  			Resources: []apiserver.ResourceConfiguration{{
   628  				Resources: []string{
   629  					"secrets",
   630  					"secrets",
   631  				},
   632  				Providers: []apiserver.ProviderConfiguration{{
   633  					KMS: &apiserver.KMSConfiguration{
   634  						Name:       "foo",
   635  						Endpoint:   "unix:///tmp/kms-provider.socket",
   636  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   637  						CacheSize:  &cacheSize,
   638  						APIVersion: "v1",
   639  					},
   640  				}},
   641  			}},
   642  		},
   643  		reload: false,
   644  		want: field.ErrorList{
   645  			field.Invalid(
   646  				root.Index(0).Child("resources"),
   647  				[]string{
   648  					"secrets",
   649  					"secrets",
   650  				},
   651  				duplicateResourceErr,
   652  			),
   653  		},
   654  	}, {
   655  		desc: "should error once when secrets are specified many times within the same resource list",
   656  		in: &apiserver.EncryptionConfiguration{
   657  			Resources: []apiserver.ResourceConfiguration{{
   658  				Resources: []string{
   659  					"secrets",
   660  					"secrets",
   661  					"secrets",
   662  					"secrets",
   663  				},
   664  				Providers: []apiserver.ProviderConfiguration{{
   665  					KMS: &apiserver.KMSConfiguration{
   666  						Name:       "foo",
   667  						Endpoint:   "unix:///tmp/kms-provider.socket",
   668  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   669  						CacheSize:  &cacheSize,
   670  						APIVersion: "v1",
   671  					},
   672  				}},
   673  			}},
   674  		},
   675  		reload: false,
   676  		want: field.ErrorList{
   677  			field.Invalid(
   678  				root.Index(0).Child("resources"),
   679  				[]string{
   680  					"secrets",
   681  					"secrets",
   682  					"secrets",
   683  					"secrets",
   684  				},
   685  				duplicateResourceErr,
   686  			),
   687  		},
   688  	}, {
   689  		desc: "should error when secrets are specified twice within the same resource list, via dot",
   690  		in: &apiserver.EncryptionConfiguration{
   691  			Resources: []apiserver.ResourceConfiguration{{
   692  				Resources: []string{
   693  					"secrets",
   694  					"secrets.",
   695  				},
   696  				Providers: []apiserver.ProviderConfiguration{{
   697  					KMS: &apiserver.KMSConfiguration{
   698  						Name:       "foo",
   699  						Endpoint:   "unix:///tmp/kms-provider.socket",
   700  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   701  						CacheSize:  &cacheSize,
   702  						APIVersion: "v1",
   703  					},
   704  				}},
   705  			}},
   706  		},
   707  		reload: false,
   708  		want: field.ErrorList{
   709  			field.Invalid(
   710  				root.Index(0).Child("resources"),
   711  				[]string{
   712  					"secrets",
   713  					"secrets.",
   714  				},
   715  				duplicateResourceErr,
   716  			),
   717  		},
   718  	}, {
   719  		desc: "should error when '*.apps' and '*.' and '*.*' are used within the same resource list",
   720  		in: &apiserver.EncryptionConfiguration{
   721  			Resources: []apiserver.ResourceConfiguration{{
   722  				Resources: []string{
   723  					"*.apps",
   724  					"*.",
   725  					"*.*",
   726  				},
   727  				Providers: []apiserver.ProviderConfiguration{{
   728  					KMS: &apiserver.KMSConfiguration{
   729  						Name:       "foo",
   730  						Endpoint:   "unix:///tmp/kms-provider.socket",
   731  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   732  						CacheSize:  &cacheSize,
   733  						APIVersion: "v1",
   734  					},
   735  				}},
   736  			}},
   737  		},
   738  		reload: false,
   739  		want: field.ErrorList{
   740  			field.Invalid(
   741  				root.Index(0).Child("resources"),
   742  				[]string{
   743  					"*.apps",
   744  					"*.",
   745  					"*.*",
   746  				},
   747  				overlapErr,
   748  			),
   749  		},
   750  	}, {
   751  		desc: "should not error when deployments.apps are specified with '*.' within the same resource list",
   752  		in: &apiserver.EncryptionConfiguration{
   753  			Resources: []apiserver.ResourceConfiguration{{
   754  				Resources: []string{
   755  					"deployments.apps",
   756  					"*.",
   757  				},
   758  				Providers: []apiserver.ProviderConfiguration{{
   759  					KMS: &apiserver.KMSConfiguration{
   760  						Name:       "foo",
   761  						Endpoint:   "unix:///tmp/kms-provider.socket",
   762  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   763  						CacheSize:  &cacheSize,
   764  						APIVersion: "v1",
   765  					},
   766  				}},
   767  			}},
   768  		},
   769  		reload: false,
   770  		want:   field.ErrorList{},
   771  	}, {
   772  		desc: "should error when deployments.apps are specified with '*.apps' within the same resource list",
   773  		in: &apiserver.EncryptionConfiguration{
   774  			Resources: []apiserver.ResourceConfiguration{{
   775  				Resources: []string{
   776  					"deployments.apps",
   777  					"*.apps",
   778  				},
   779  				Providers: []apiserver.ProviderConfiguration{{
   780  					KMS: &apiserver.KMSConfiguration{
   781  						Name:       "foo",
   782  						Endpoint:   "unix:///tmp/kms-provider.socket",
   783  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   784  						CacheSize:  &cacheSize,
   785  						APIVersion: "v1",
   786  					},
   787  				}},
   788  			}},
   789  		},
   790  		reload: false,
   791  		want: field.ErrorList{
   792  			field.Invalid(
   793  				root.Index(0).Child("resources"),
   794  				[]string{
   795  					"deployments.apps",
   796  					"*.apps",
   797  				},
   798  				overlapErr,
   799  			),
   800  		},
   801  	}, {
   802  		desc: "should error when secrets are specified with '*.' within the same resource list",
   803  		in: &apiserver.EncryptionConfiguration{
   804  			Resources: []apiserver.ResourceConfiguration{{
   805  				Resources: []string{
   806  					"secrets",
   807  					"*.",
   808  				},
   809  				Providers: []apiserver.ProviderConfiguration{{
   810  					KMS: &apiserver.KMSConfiguration{
   811  						Name:       "foo",
   812  						Endpoint:   "unix:///tmp/kms-provider.socket",
   813  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   814  						CacheSize:  &cacheSize,
   815  						APIVersion: "v1",
   816  					},
   817  				}},
   818  			}},
   819  		},
   820  		reload: false,
   821  		want: field.ErrorList{
   822  			field.Invalid(
   823  				root.Index(0).Child("resources"),
   824  				[]string{
   825  					"secrets",
   826  					"*.",
   827  				},
   828  				overlapErr,
   829  			),
   830  		},
   831  	}, {
   832  		desc: "should error when pods are specified with '*.' within the same resource list",
   833  		in: &apiserver.EncryptionConfiguration{
   834  			Resources: []apiserver.ResourceConfiguration{{
   835  				Resources: []string{
   836  					"pods",
   837  					"*.",
   838  				},
   839  				Providers: []apiserver.ProviderConfiguration{{
   840  					KMS: &apiserver.KMSConfiguration{
   841  						Name:       "foo",
   842  						Endpoint:   "unix:///tmp/kms-provider.socket",
   843  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   844  						CacheSize:  &cacheSize,
   845  						APIVersion: "v1",
   846  					},
   847  				}},
   848  			}},
   849  		},
   850  		reload: false,
   851  		want: field.ErrorList{
   852  			field.Invalid(
   853  				root.Index(0).Child("resources"),
   854  				[]string{
   855  					"pods",
   856  					"*.",
   857  				},
   858  				overlapErr,
   859  			),
   860  		},
   861  	}, {
   862  		desc: "should error when other resources are specified with '*.*' within the same resource list",
   863  		in: &apiserver.EncryptionConfiguration{
   864  			Resources: []apiserver.ResourceConfiguration{{
   865  				Resources: []string{
   866  					"secrets",
   867  					"*.*",
   868  				},
   869  				Providers: []apiserver.ProviderConfiguration{{
   870  					KMS: &apiserver.KMSConfiguration{
   871  						Name:       "foo",
   872  						Endpoint:   "unix:///tmp/kms-provider.socket",
   873  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   874  						CacheSize:  &cacheSize,
   875  						APIVersion: "v1",
   876  					},
   877  				}},
   878  			}},
   879  		},
   880  		reload: false,
   881  		want: field.ErrorList{
   882  			field.Invalid(
   883  				root.Index(0).Child("resources"),
   884  				[]string{
   885  					"secrets",
   886  					"*.*",
   887  				},
   888  				overlapErr,
   889  			),
   890  		},
   891  	}, {
   892  		desc: "should error when both '*.' and '*.*' are used within the same resource list",
   893  		in: &apiserver.EncryptionConfiguration{
   894  			Resources: []apiserver.ResourceConfiguration{{
   895  				Resources: []string{
   896  					"*.",
   897  					"*.*",
   898  				},
   899  				Providers: []apiserver.ProviderConfiguration{{
   900  					KMS: &apiserver.KMSConfiguration{
   901  						Name:       "foo",
   902  						Endpoint:   "unix:///tmp/kms-provider.socket",
   903  						Timeout:    &metav1.Duration{Duration: 3 * time.Second},
   904  						CacheSize:  &cacheSize,
   905  						APIVersion: "v1",
   906  					},
   907  				}},
   908  			}},
   909  		},
   910  		reload: false,
   911  		want: field.ErrorList{
   912  			field.Invalid(
   913  				root.Index(0).Child("resources"),
   914  				[]string{
   915  					"*.",
   916  					"*.*",
   917  				},
   918  				overlapErr,
   919  			),
   920  		},
   921  	}}
   922  
   923  	for _, tt := range testCases {
   924  		t.Run(tt.desc, func(t *testing.T) {
   925  			got := ValidateEncryptionConfiguration(tt.in, tt.reload)
   926  			if d := cmp.Diff(tt.want, got); d != "" {
   927  				t.Fatalf("EncryptionConfiguration validation results mismatch (-want +got):\n%s", d)
   928  			}
   929  		})
   930  	}
   931  }
   932  
   933  func TestKey(t *testing.T) {
   934  	root := field.NewPath("resources")
   935  	path := root.Index(0).Child("provider").Index(0).Child("key").Index(0)
   936  	testCases := []struct {
   937  		desc string
   938  		in   apiserver.Key
   939  		want field.ErrorList
   940  	}{{
   941  		desc: "valid key",
   942  		in:   apiserver.Key{Name: "foo", Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="},
   943  		want: field.ErrorList{},
   944  	}, {
   945  		desc: "key without name",
   946  		in:   apiserver.Key{Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="},
   947  		want: field.ErrorList{
   948  			field.Required(path.Child("name"), fmt.Sprintf(mandatoryFieldErrFmt, "name", "key")),
   949  		},
   950  	}, {
   951  		desc: "key without secret",
   952  		in:   apiserver.Key{Name: "foo"},
   953  		want: field.ErrorList{
   954  			field.Required(path.Child("secret"), fmt.Sprintf(mandatoryFieldErrFmt, "secret", "key")),
   955  		},
   956  	}, {
   957  		desc: "key is not base64 encoded",
   958  		in:   apiserver.Key{Name: "foo", Secret: "P@ssword"},
   959  		want: field.ErrorList{
   960  			field.Invalid(path.Child("secret"), "REDACTED", base64EncodingErr),
   961  		},
   962  	}, {
   963  		desc: "key is not of expected length",
   964  		in:   apiserver.Key{Name: "foo", Secret: "cGFzc3dvcmQK"},
   965  		want: field.ErrorList{
   966  			field.Invalid(path.Child("secret"), "REDACTED", fmt.Sprintf(keyLenErrFmt, 9, aesKeySizes)),
   967  		},
   968  	}}
   969  
   970  	for _, tt := range testCases {
   971  		t.Run(tt.desc, func(t *testing.T) {
   972  			got := validateKey(tt.in, path, aesKeySizes)
   973  			if d := cmp.Diff(tt.want, got); d != "" {
   974  				t.Fatalf("Key validation results mismatch (-want +got):\n%s", d)
   975  			}
   976  		})
   977  	}
   978  }
   979  
   980  func TestKMSProviderTimeout(t *testing.T) {
   981  	timeoutField := field.NewPath("Resource").Index(0).Child("Provider").Index(0).Child("KMS").Child("Timeout")
   982  	negativeTimeout := &metav1.Duration{Duration: -1 * time.Minute}
   983  	zeroTimeout := &metav1.Duration{Duration: 0 * time.Minute}
   984  
   985  	testCases := []struct {
   986  		desc string
   987  		in   *apiserver.KMSConfiguration
   988  		want field.ErrorList
   989  	}{{
   990  		desc: "valid timeout",
   991  		in:   &apiserver.KMSConfiguration{Timeout: &metav1.Duration{Duration: 1 * time.Minute}},
   992  		want: field.ErrorList{},
   993  	}, {
   994  		desc: "negative timeout",
   995  		in:   &apiserver.KMSConfiguration{Timeout: negativeTimeout},
   996  		want: field.ErrorList{
   997  			field.Invalid(timeoutField, negativeTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")),
   998  		},
   999  	}, {
  1000  		desc: "zero timeout",
  1001  		in:   &apiserver.KMSConfiguration{Timeout: zeroTimeout},
  1002  		want: field.ErrorList{
  1003  			field.Invalid(timeoutField, zeroTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")),
  1004  		},
  1005  	}}
  1006  
  1007  	for _, tt := range testCases {
  1008  		t.Run(tt.desc, func(t *testing.T) {
  1009  			got := validateKMSTimeout(tt.in, timeoutField)
  1010  			if d := cmp.Diff(tt.want, got); d != "" {
  1011  				t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
  1012  			}
  1013  		})
  1014  	}
  1015  }
  1016  
  1017  func TestKMSEndpoint(t *testing.T) {
  1018  	endpointField := field.NewPath("Resource").Index(0).Child("Provider").Index(0).Child("kms").Child("endpoint")
  1019  	testCases := []struct {
  1020  		desc string
  1021  		in   *apiserver.KMSConfiguration
  1022  		want field.ErrorList
  1023  	}{{
  1024  		desc: "valid endpoint",
  1025  		in:   &apiserver.KMSConfiguration{Endpoint: "unix:///socket.sock"},
  1026  		want: field.ErrorList{},
  1027  	}, {
  1028  		desc: "empty endpoint",
  1029  		in:   &apiserver.KMSConfiguration{},
  1030  		want: field.ErrorList{
  1031  			field.Invalid(endpointField, "", fmt.Sprintf(mandatoryFieldErrFmt, "endpoint", "kms")),
  1032  		},
  1033  	}, {
  1034  		desc: "non unix endpoint",
  1035  		in:   &apiserver.KMSConfiguration{Endpoint: "https://www.foo.com"},
  1036  		want: field.ErrorList{
  1037  			field.Invalid(endpointField, "https://www.foo.com", fmt.Sprintf(unsupportedSchemeErrFmt, "https")),
  1038  		},
  1039  	}, {
  1040  		desc: "invalid url",
  1041  		in:   &apiserver.KMSConfiguration{Endpoint: "unix:///foo\n.socket"},
  1042  		want: field.ErrorList{
  1043  			field.Invalid(endpointField, "unix:///foo\n.socket", fmt.Sprintf(invalidURLErrFmt, `parse "unix:///foo\n.socket": net/url: invalid control character in URL`)),
  1044  		},
  1045  	}}
  1046  
  1047  	for _, tt := range testCases {
  1048  		t.Run(tt.desc, func(t *testing.T) {
  1049  			got := validateKMSEndpoint(tt.in, endpointField)
  1050  			if d := cmp.Diff(tt.want, got); d != "" {
  1051  				t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
  1052  			}
  1053  		})
  1054  	}
  1055  }
  1056  
  1057  func TestKMSProviderCacheSize(t *testing.T) {
  1058  	root := field.NewPath("resources")
  1059  	cacheField := root.Index(0).Child("kms").Child("cachesize")
  1060  	negativeCacheSize := int32(-1)
  1061  	positiveCacheSize := int32(10)
  1062  	zeroCacheSize := int32(0)
  1063  
  1064  	testCases := []struct {
  1065  		desc string
  1066  		in   *apiserver.KMSConfiguration
  1067  		want field.ErrorList
  1068  	}{{
  1069  		desc: "valid positive cache size",
  1070  		in:   &apiserver.KMSConfiguration{APIVersion: "v1", CacheSize: &positiveCacheSize},
  1071  		want: field.ErrorList{},
  1072  	}, {
  1073  		desc: "invalid zero cache size",
  1074  		in:   &apiserver.KMSConfiguration{APIVersion: "v1", CacheSize: &zeroCacheSize},
  1075  		want: field.ErrorList{
  1076  			field.Invalid(cacheField, int32(0), fmt.Sprintf(nonZeroErrFmt, "cachesize")),
  1077  		},
  1078  	}, {
  1079  		desc: "valid negative caches size",
  1080  		in:   &apiserver.KMSConfiguration{APIVersion: "v1", CacheSize: &negativeCacheSize},
  1081  		want: field.ErrorList{},
  1082  	}, {
  1083  		desc: "cache size set with v2 provider",
  1084  		in:   &apiserver.KMSConfiguration{CacheSize: &positiveCacheSize, APIVersion: "v2"},
  1085  		want: field.ErrorList{
  1086  			field.Invalid(cacheField, positiveCacheSize, "cachesize is not supported in v2"),
  1087  		},
  1088  	}}
  1089  
  1090  	for _, tt := range testCases {
  1091  		t.Run(tt.desc, func(t *testing.T) {
  1092  			got := validateKMSCacheSize(tt.in, cacheField)
  1093  			if d := cmp.Diff(tt.want, got); d != "" {
  1094  				t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
  1095  			}
  1096  		})
  1097  	}
  1098  }
  1099  
  1100  func TestKMSProviderAPIVersion(t *testing.T) {
  1101  	apiVersionField := field.NewPath("Resource").Index(0).Child("Provider").Index(0).Child("KMS").Child("APIVersion")
  1102  
  1103  	testCases := []struct {
  1104  		desc string
  1105  		in   *apiserver.KMSConfiguration
  1106  		want field.ErrorList
  1107  	}{{
  1108  		desc: "valid v1 api version",
  1109  		in:   &apiserver.KMSConfiguration{APIVersion: "v1"},
  1110  		want: field.ErrorList{},
  1111  	}, {
  1112  		desc: "valid v2 api version",
  1113  		in:   &apiserver.KMSConfiguration{APIVersion: "v2"},
  1114  		want: field.ErrorList{},
  1115  	}, {
  1116  		desc: "invalid api version",
  1117  		in:   &apiserver.KMSConfiguration{APIVersion: "v3"},
  1118  		want: field.ErrorList{
  1119  			field.Invalid(apiVersionField, "v3", fmt.Sprintf(unsupportedKMSAPIVersionErrFmt, "apiVersion")),
  1120  		},
  1121  	}}
  1122  
  1123  	for _, tt := range testCases {
  1124  		t.Run(tt.desc, func(t *testing.T) {
  1125  			got := validateKMSAPIVersion(tt.in, apiVersionField)
  1126  			if d := cmp.Diff(tt.want, got); d != "" {
  1127  				t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
  1128  			}
  1129  		})
  1130  	}
  1131  }
  1132  
  1133  func TestKMSProviderName(t *testing.T) {
  1134  	nameField := field.NewPath("Resource").Index(0).Child("Provider").Index(0).Child("KMS").Child("name")
  1135  
  1136  	testCases := []struct {
  1137  		desc             string
  1138  		in               *apiserver.KMSConfiguration
  1139  		reload           bool
  1140  		kmsProviderNames sets.Set[string]
  1141  		want             field.ErrorList
  1142  	}{{
  1143  		desc: "valid name",
  1144  		in:   &apiserver.KMSConfiguration{Name: "foo"},
  1145  		want: field.ErrorList{},
  1146  	}, {
  1147  		desc: "empty name",
  1148  		in:   &apiserver.KMSConfiguration{},
  1149  		want: field.ErrorList{
  1150  			field.Required(nameField, fmt.Sprintf(mandatoryFieldErrFmt, "name", "provider")),
  1151  		},
  1152  	}, {
  1153  		desc: "invalid name with :",
  1154  		in:   &apiserver.KMSConfiguration{Name: "foo:bar"},
  1155  		want: field.ErrorList{
  1156  			field.Invalid(nameField, "foo:bar", fmt.Sprintf(invalidKMSConfigNameErrFmt, "foo:bar")),
  1157  		},
  1158  	}, {
  1159  		desc: "invalid name with : but api version is v1",
  1160  		in:   &apiserver.KMSConfiguration{Name: "foo:bar", APIVersion: "v1"},
  1161  		want: field.ErrorList{},
  1162  	}, {
  1163  		desc:             "duplicate name, kms v2, reload=false",
  1164  		in:               &apiserver.KMSConfiguration{APIVersion: "v2", Name: "foo"},
  1165  		kmsProviderNames: sets.New("foo"),
  1166  		want: field.ErrorList{
  1167  			field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
  1168  		},
  1169  	}, {
  1170  		desc:             "duplicate name, kms v2, reload=true",
  1171  		in:               &apiserver.KMSConfiguration{APIVersion: "v2", Name: "foo"},
  1172  		reload:           true,
  1173  		kmsProviderNames: sets.New("foo"),
  1174  		want: field.ErrorList{
  1175  			field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
  1176  		},
  1177  	}, {
  1178  		desc:             "duplicate name, kms v1, reload=false",
  1179  		in:               &apiserver.KMSConfiguration{APIVersion: "v1", Name: "foo"},
  1180  		kmsProviderNames: sets.New("foo"),
  1181  		want:             field.ErrorList{},
  1182  	}, {
  1183  		desc:             "duplicate name, kms v1, reload=true",
  1184  		in:               &apiserver.KMSConfiguration{APIVersion: "v1", Name: "foo"},
  1185  		reload:           true,
  1186  		kmsProviderNames: sets.New("foo"),
  1187  		want: field.ErrorList{
  1188  			field.Invalid(nameField, "foo", fmt.Sprintf(duplicateKMSConfigNameErrFmt, "foo")),
  1189  		},
  1190  	}}
  1191  
  1192  	for _, tt := range testCases {
  1193  		t.Run(tt.desc, func(t *testing.T) {
  1194  			got := validateKMSConfigName(tt.in, nameField, tt.kmsProviderNames, tt.reload)
  1195  			if d := cmp.Diff(tt.want, got); d != "" {
  1196  				t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
  1197  			}
  1198  		})
  1199  	}
  1200  }