Merge pull request #41837 from liggitt/storageclass-param-validation

Automatic merge from submit-queue

Reserve kubernetes.io and k8s.io namespace for flex volume options

Split from https://github.com/kubernetes/kubernetes/pull/39488.

Flex volume already stuffs system information into the options map, and assumes it is free to do so:
```
	optionFSType    = "kubernetes.io/fsType"
	optionReadWrite = "kubernetes.io/readwrite"
	optionKeySecret = "kubernetes.io/secret"
```

this formalizes that by reserving the `kubernetes.io` and `k8s.io` namespaces so that user-specified options are never stomped by the system, and flex plugins can know that options with those namespaces came from the system, not user-options.

```release-note
Parameter keys in a StorageClass `parameters` map may not use the `kubernetes.io` or `k8s.io` namespaces.
```
pull/6/head
Kubernetes Submit Queue 2017-02-28 02:41:03 -08:00 committed by GitHub
commit d33f6b8a17
2 changed files with 96 additions and 0 deletions

View File

@ -920,6 +920,19 @@ func validateFlexVolumeSource(fv *api.FlexVolumeSource, fldPath *field.Path) fie
if len(fv.Driver) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("driver"), ""))
}
// Make sure user-specified options don't use kubernetes namespaces
for k := range fv.Options {
namespace := k
if parts := strings.SplitN(k, "/", 2); len(parts) == 2 {
namespace = parts[0]
}
normalized := "." + strings.ToLower(namespace)
if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") {
allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved"))
}
}
return allErrs
}

View File

@ -8775,3 +8775,86 @@ func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
t.Error("Endpoint should accept a NodeName that is an IP address")
}
}
func TestValidateFlexVolumeSource(t *testing.T) {
testcases := map[string]struct {
source *api.FlexVolumeSource
expectedErrs map[string]string
}{
"valid": {
source: &api.FlexVolumeSource{Driver: "foo"},
expectedErrs: map[string]string{},
},
"valid with options": {
source: &api.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}},
expectedErrs: map[string]string{},
},
"no driver": {
source: &api.FlexVolumeSource{Driver: ""},
expectedErrs: map[string]string{"driver": "Required value"},
},
"reserved option keys": {
source: &api.FlexVolumeSource{
Driver: "foo",
Options: map[string]string{
// valid options
"myns.io": "A",
"myns.io/bar": "A",
"myns.io/kubernetes.io": "A",
// invalid options
"KUBERNETES.IO": "A",
"kubernetes.io": "A",
"kubernetes.io/": "A",
"kubernetes.io/foo": "A",
"alpha.kubernetes.io": "A",
"alpha.kubernetes.io/": "A",
"alpha.kubernetes.io/foo": "A",
"k8s.io": "A",
"k8s.io/": "A",
"k8s.io/foo": "A",
"alpha.k8s.io": "A",
"alpha.k8s.io/": "A",
"alpha.k8s.io/foo": "A",
},
},
expectedErrs: map[string]string{
"options[KUBERNETES.IO]": "reserved",
"options[kubernetes.io]": "reserved",
"options[kubernetes.io/]": "reserved",
"options[kubernetes.io/foo]": "reserved",
"options[alpha.kubernetes.io]": "reserved",
"options[alpha.kubernetes.io/]": "reserved",
"options[alpha.kubernetes.io/foo]": "reserved",
"options[k8s.io]": "reserved",
"options[k8s.io/]": "reserved",
"options[k8s.io/foo]": "reserved",
"options[alpha.k8s.io]": "reserved",
"options[alpha.k8s.io/]": "reserved",
"options[alpha.k8s.io/foo]": "reserved",
},
},
}
for k, tc := range testcases {
errs := validateFlexVolumeSource(tc.source, nil)
for _, err := range errs {
expectedErr, ok := tc.expectedErrs[err.Field]
if !ok {
t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err)
continue
}
if !strings.Contains(err.Error(), expectedErr) {
t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error())
continue
}
}
if len(errs) != len(tc.expectedErrs) {
t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs)
continue
}
}
}