diff --git a/go.mod b/go.mod index 7af642da..5c380f58 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 google.golang.org/grpc v1.38.0 k8s.io/api v0.22.0-beta.1 + k8s.io/apimachinery v0.22.0-beta.1 k8s.io/client-go v0.22.0-beta.1 k8s.io/component-base v0.22.0-beta.1 k8s.io/klog/v2 v2.9.0 diff --git a/params/common.go b/params/common.go new file mode 100644 index 00000000..07d2ae1a --- /dev/null +++ b/params/common.go @@ -0,0 +1,114 @@ +/* +Copyright 2021 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 params + +import ( + "fmt" + "os" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" +) + +//SecretParamsMap provides a mapping of current as well as deprecated secret keys +type SecretParamsMap struct { + name string + deprecatedSecretNameKey string + deprecatedSecretNamespaceKey string + secretNameKey string + secretNamespaceKey string +} + +func (s SecretParamsMap) GetSecretNameKey() string { + return s.secretNameKey +} + +func (s SecretParamsMap) GetSecretNamespaceKey() string { + return s.secretNamespaceKey +} + +// VerifyAndGetSecretNameAndNamespaceTemplate gets the values (templates) associated +// with the parameters specified in "secret" and verifies that they are specified correctly. +func VerifyAndGetSecretNameAndNamespaceTemplate(secret SecretParamsMap, templateParams map[string]string) (nameTemplate, namespaceTemplate string, err error) { + numName := 0 + numNamespace := 0 + + if t, ok := templateParams[secret.deprecatedSecretNameKey]; ok { + nameTemplate = t + numName++ + klog.Warning(deprecationWarning(secret.deprecatedSecretNameKey, secret.secretNameKey, "")) + } + if t, ok := templateParams[secret.deprecatedSecretNamespaceKey]; ok { + namespaceTemplate = t + numNamespace++ + klog.Warning(deprecationWarning(secret.deprecatedSecretNamespaceKey, secret.secretNamespaceKey, "")) + } + if t, ok := templateParams[secret.secretNameKey]; ok { + nameTemplate = t + numName++ + } + if t, ok := templateParams[secret.secretNamespaceKey]; ok { + namespaceTemplate = t + numNamespace++ + } + + if numName > 1 || numNamespace > 1 { + // Double specified error + return "", "", fmt.Errorf("%s secrets specified in parameters with both \"csi\" and \"%s\" keys", secret.name, csiParameterPrefix) + } else if numName != numNamespace { + // Not both 0 or both 1 + return "", "", fmt.Errorf("either name and namespace for %s secrets specified, Both must be specified", secret.name) + } else if numName == 1 { + // Case where we've found a name and a namespace template + if nameTemplate == "" || namespaceTemplate == "" { + return "", "", fmt.Errorf("%s secrets specified in parameters but value of either namespace or name is empty", secret.name) + } + return nameTemplate, namespaceTemplate, nil + } else if numName == 0 { + // No secrets specified + return "", "", nil + } else { + // THIS IS NOT A VALID CASE + return "", "", fmt.Errorf("unknown error with getting secret name and namespace templates") + } +} + +func resolveTemplate(template string, params map[string]string) (string, error) { + missingParams := sets.NewString() + resolved := os.Expand(template, func(k string) string { + v, ok := params[k] + if !ok { + missingParams.Insert(k) + } + return v + }) + if missingParams.Len() > 0 { + return "", fmt.Errorf("invalid tokens: %q", missingParams.List()) + } + return resolved, nil +} + +func deprecationWarning(deprecatedParam, newParam, removalVersion string) string { + if removalVersion == "" { + removalVersion = "a future release" + } + newParamPhrase := "" + if len(newParam) != 0 { + newParamPhrase = fmt.Sprintf(", please use \"%s\" instead", newParam) + } + return fmt.Sprintf("\"%s\" is deprecated and will be removed in %s%s", deprecatedParam, removalVersion, newParamPhrase) +} diff --git a/params/const.go b/params/const.go new file mode 100644 index 00000000..de5c336e --- /dev/null +++ b/params/const.go @@ -0,0 +1,73 @@ +/* +Copyright 2021 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 params + +const ( + // CSI Parameters prefixed with csiParameterPrefix are not passed through + // to the driver on CreateVolumeRequest calls. Instead they are intended + // to used by the CSI external-provisioner and maybe used to populate + // fields in subsequent CSI calls or Kubernetes API objects. + csiParameterPrefix = "csi.storage.k8s.io/" + + PrefixedFsTypeKey = csiParameterPrefix + "fstype" + + prefixedDefaultSecretNameKey = csiParameterPrefix + "secret-name" + prefixedDefaultSecretNamespaceKey = csiParameterPrefix + "secret-namespace" + + prefixedProvisionerSecretNameKey = csiParameterPrefix + "provisioner-secret-name" + prefixedProvisionerSecretNamespaceKey = csiParameterPrefix + "provisioner-secret-namespace" + + prefixedControllerPublishSecretNameKey = csiParameterPrefix + "controller-publish-secret-name" + prefixedControllerPublishSecretNamespaceKey = csiParameterPrefix + "controller-publish-secret-namespace" + + prefixedNodeStageSecretNameKey = csiParameterPrefix + "node-stage-secret-name" + prefixedNodeStageSecretNamespaceKey = csiParameterPrefix + "node-stage-secret-namespace" + + prefixedNodePublishSecretNameKey = csiParameterPrefix + "node-publish-secret-name" + prefixedNodePublishSecretNamespaceKey = csiParameterPrefix + "node-publish-secret-namespace" + + prefixedControllerExpandSecretNameKey = csiParameterPrefix + "controller-expand-secret-name" + prefixedControllerExpandSecretNamespaceKey = csiParameterPrefix + "controller-expand-secret-namespace" + + prefixedSnapshotterSecretNameKey = csiParameterPrefix + "snapshotter-secret-name" + prefixedSnapshotterSecretNamespaceKey = csiParameterPrefix + "snapshotter-secret-namespace" + + prefixedSnapshotterListSecretNameKey = csiParameterPrefix + "snapshotter-list-secret-name" + prefixedSnapshotterListSecretNamespaceKey = csiParameterPrefix + "snapshotter-list-secret-namespace" + + prefixedVolumeSnapshotNameKey = csiParameterPrefix + "volumesnapshot/name" + prefixedVolumeSnapshotNamespaceKey = csiParameterPrefix + "volumesnapshot/namespace" + prefixedVolumeSnapshotContentNameKey = csiParameterPrefix + "volumesnapshotcontent/name" + + // [Deprecated] CSI Parameters that are put into fields but + // NOT stripped from the parameters passed to CreateVolume + provisionerSecretNameKey = "csiProvisionerSecretName" + provisionerSecretNamespaceKey = "csiProvisionerSecretNamespace" + + controllerPublishSecretNameKey = "csiControllerPublishSecretName" + controllerPublishSecretNamespaceKey = "csiControllerPublishSecretNamespace" + + nodeStageSecretNameKey = "csiNodeStageSecretName" + nodeStageSecretNamespaceKey = "csiNodeStageSecretNamespace" + + nodePublishSecretNameKey = "csiNodePublishSecretName" + nodePublishSecretNamespaceKey = "csiNodePublishSecretNamespace" + + tokenPVNameKey = "pv.name" + tokenPVCNameKey = "pvc.name" + tokenPVCNameSpaceKey = "pvc.namespace" +) diff --git a/params/snapshot.go b/params/snapshot.go new file mode 100644 index 00000000..5841369f --- /dev/null +++ b/params/snapshot.go @@ -0,0 +1,139 @@ +/* +Copyright 2021 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 params + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/klog/v2" +) + +var ( + SnapshotterSecretParams = SecretParamsMap{ + name: "Snapshotter", + secretNameKey: prefixedSnapshotterSecretNameKey, + secretNamespaceKey: prefixedSnapshotterSecretNamespaceKey, + } + + SnapshotterListSecretParams = SecretParamsMap{ + name: "SnapshotterList", + secretNameKey: prefixedSnapshotterListSecretNameKey, + secretNamespaceKey: prefixedSnapshotterListSecretNamespaceKey, + } +) + +// GetSnapshotSecretReference returns a reference to the secret specified in the given nameTemplate +// and namespaceTemplate, or an error if the templates are not specified correctly. +// No lookup of the referenced secret is performed, and the secret may or may not exist. +// +// supported tokens for name resolution: +// - ${volumesnapshotcontent.name} +// - ${volumesnapshot.namespace} +// - ${volumesnapshot.name} +// +// supported tokens for namespace resolution: +// - ${volumesnapshotcontent.name} +// - ${volumesnapshot.namespace} +// +// an error is returned in the following situations: +// - the nameTemplate or namespaceTemplate contains a token that cannot be resolved +// - the resolved name is not a valid secret name +// - the resolved namespace is not a valid namespace name +func GetSnapshotSecretReference(secretParams SecretParamsMap, snapshotClassParams map[string]string, snapContentName string, snapNamespace string, snapName string) (*v1.SecretReference, error) { + nameTemplate, namespaceTemplate, err := VerifyAndGetSecretNameAndNamespaceTemplate(secretParams, snapshotClassParams) + if err != nil { + return nil, fmt.Errorf("failed to get name and namespace template from params: %v", err) + } + + if nameTemplate == "" && namespaceTemplate == "" { + return nil, nil + } + + ref := &v1.SecretReference{} + + // Secret namespace template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace. + // Note that neither of those things are under the control of the VolumeSnapshot user. + namespaceParams := map[string]string{"volumesnapshotcontent.name": snapContentName} + // snapshot may be nil when resolving create/delete snapshot secret names because the + // snapshot may or may not exist at delete time + if snapNamespace != "" { + namespaceParams["volumesnapshot.namespace"] = snapNamespace + } + + resolvedNamespace, err := resolveTemplate(namespaceTemplate, namespaceParams) + if err != nil { + return nil, fmt.Errorf("error resolving value %q: %v", namespaceTemplate, err) + } + klog.V(4).Infof("GetSecretReference namespaceTemplate %s, namespaceParams: %+v, resolved %s", namespaceTemplate, namespaceParams, resolvedNamespace) + + if len(validation.IsDNS1123Label(resolvedNamespace)) > 0 { + if namespaceTemplate != resolvedNamespace { + return nil, fmt.Errorf("%q resolved to %q which is not a valid namespace name", namespaceTemplate, resolvedNamespace) + } + return nil, fmt.Errorf("%q is not a valid namespace name", namespaceTemplate) + } + ref.Namespace = resolvedNamespace + + // Secret name template can make use of the VolumeSnapshotContent name, VolumeSnapshot name or namespace. + // Note that VolumeSnapshot name and namespace are under the VolumeSnapshot user's control. + nameParams := map[string]string{"volumesnapshotcontent.name": snapContentName} + if snapName != "" { + nameParams["volumesnapshot.name"] = snapName + } + if snapNamespace != "" { + nameParams["volumesnapshot.namespace"] = snapNamespace + } + resolvedName, err := resolveTemplate(nameTemplate, nameParams) + if err != nil { + return nil, fmt.Errorf("error resolving value %q: %v", nameTemplate, err) + } + if len(validation.IsDNS1123Subdomain(resolvedName)) > 0 { + if nameTemplate != resolvedName { + return nil, fmt.Errorf("%q resolved to %q which is not a valid secret name", nameTemplate, resolvedName) + } + return nil, fmt.Errorf("%q is not a valid secret name", nameTemplate) + } + ref.Name = resolvedName + + klog.V(4).Infof("GetSecretReference validated Secret: %+v", ref) + return ref, nil +} + +func RemovePrefixedSnapshotParameters(param map[string]string) (map[string]string, error) { + newParam := map[string]string{} + for k, v := range param { + if strings.HasPrefix(k, csiParameterPrefix) { + // Check if its well known + switch k { + case prefixedSnapshotterSecretNameKey: + case prefixedSnapshotterSecretNamespaceKey: + case prefixedSnapshotterListSecretNameKey: + case prefixedSnapshotterListSecretNamespaceKey: + default: + return map[string]string{}, fmt.Errorf("found unknown parameter key \"%s\" with reserved namespace %s", k, csiParameterPrefix) + } + } else { + // Don't strip, add this key-value to new map + // Deprecated parameters prefixed with "csi" are not stripped to preserve backwards compatibility + newParam[k] = v + } + } + return newParam, nil +} diff --git a/params/snapshot_test.go b/params/snapshot_test.go new file mode 100644 index 00000000..9d670ee1 --- /dev/null +++ b/params/snapshot_test.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 params + +import ( + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" +) + +func TestGetSnapshotSecretReference(t *testing.T) { + testcases := map[string]struct { + secretParams SecretParamsMap + params map[string]string + snapContentName string + snapNamespace string + snapName string + expectRef *v1.SecretReference + expectErr bool + }{ + "no params": { + secretParams: SnapshotterSecretParams, + params: nil, + expectRef: nil, + }, + "namespace, no name": { + secretParams: SnapshotterSecretParams, + params: map[string]string{prefixedSnapshotterSecretNamespaceKey: "foo"}, + expectErr: true, + }, + "simple - valid": { + secretParams: SnapshotterSecretParams, + params: map[string]string{prefixedSnapshotterSecretNameKey: "name", prefixedSnapshotterSecretNamespaceKey: "ns"}, + snapNamespace: "", + snapName: "", + expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"}, + }, + "simple - invalid name": { + secretParams: SnapshotterSecretParams, + params: map[string]string{prefixedSnapshotterSecretNameKey: "bad name", prefixedSnapshotterSecretNamespaceKey: "ns"}, + snapNamespace: "", + snapName: "", + expectRef: nil, + expectErr: true, + }, + "template - invalid": { + secretParams: SnapshotterSecretParams, + params: map[string]string{ + prefixedSnapshotterSecretNameKey: "static-${volumesnapshotcontent.name}-${volumesnapshot.namespace}-${volumesnapshot.name}-${volumesnapshot.annotations['akey']}", + prefixedSnapshotterSecretNamespaceKey: "static-${volumesnapshotcontent.name}-${volumesnapshot.namespace}", + }, + snapContentName: "snapcontentname", + snapNamespace: "snapshotnamespace", + snapName: "snapshotname", + expectRef: nil, + expectErr: true, + }, + } + + for k, tc := range testcases { + t.Run(k, func(t *testing.T) { + ref, err := GetSnapshotSecretReference(tc.secretParams, tc.params, tc.snapContentName, tc.snapNamespace, tc.snapName) + if err != nil { + if tc.expectErr { + return + } + t.Fatalf("Did not expect error but got: %v", err) + + } else { + if tc.expectErr { + t.Fatalf("Expected error but got none") + } + } + if !reflect.DeepEqual(ref, tc.expectRef) { + t.Errorf("Expected %v, got %v", tc.expectRef, ref) + } + }) + } +} + +func TestRemovePrefixedCSIParams(t *testing.T) { + testcases := []struct { + name string + params map[string]string + expectedParams map[string]string + expectErr bool + }{ + { + name: "no prefix", + params: map[string]string{"csiFoo": "bar", "bim": "baz"}, + expectedParams: map[string]string{"csiFoo": "bar", "bim": "baz"}, + }, + { + name: "one prefixed", + params: map[string]string{prefixedSnapshotterSecretNameKey: "bar", "bim": "baz"}, + expectedParams: map[string]string{"bim": "baz"}, + }, + { + name: "all known prefixed", + params: map[string]string{ + prefixedSnapshotterSecretNameKey: "csiBar", + prefixedSnapshotterSecretNamespaceKey: "csiBar", + prefixedSnapshotterListSecretNameKey: "csiBar", + prefixedSnapshotterListSecretNamespaceKey: "csiBar", + }, + expectedParams: map[string]string{}, + }, + { + name: "unknown prefixed var", + params: map[string]string{csiParameterPrefix + "bim": "baz"}, + expectErr: true, + }, + { + name: "empty", + params: map[string]string{}, + expectedParams: map[string]string{}, + }, + } + for _, tc := range testcases { + t.Logf("test: %v", tc.name) + newParams, err := RemovePrefixedSnapshotParameters(tc.params) + if err != nil { + if tc.expectErr { + continue + } else { + t.Fatalf("Encountered unexpected error: %v", err) + } + } else { + if tc.expectErr { + t.Fatalf("Did not get error when one was expected") + } + } + eq := reflect.DeepEqual(newParams, tc.expectedParams) + if !eq { + t.Fatalf("Stripped parameters: %v not equal to expected parameters: %v", newParams, tc.expectedParams) + } + } +} diff --git a/params/volume.go b/params/volume.go new file mode 100644 index 00000000..9d544039 --- /dev/null +++ b/params/volume.go @@ -0,0 +1,187 @@ +/* +Copyright 2021 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 params + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/validation" +) + +var ( + DefaultSecretParams = SecretParamsMap{ + name: "Default", + secretNameKey: prefixedDefaultSecretNameKey, + secretNamespaceKey: prefixedDefaultSecretNamespaceKey, + } + + ProvisionerSecretParams = SecretParamsMap{ + name: "Provisioner", + deprecatedSecretNameKey: provisionerSecretNameKey, + deprecatedSecretNamespaceKey: provisionerSecretNamespaceKey, + secretNameKey: prefixedProvisionerSecretNameKey, + secretNamespaceKey: prefixedProvisionerSecretNamespaceKey, + } + + NodePublishSecretParams = SecretParamsMap{ + name: "NodePublish", + deprecatedSecretNameKey: nodePublishSecretNameKey, + deprecatedSecretNamespaceKey: nodePublishSecretNamespaceKey, + secretNameKey: prefixedNodePublishSecretNameKey, + secretNamespaceKey: prefixedNodePublishSecretNamespaceKey, + } + + ControllerPublishSecretParams = SecretParamsMap{ + name: "ControllerPublish", + deprecatedSecretNameKey: controllerPublishSecretNameKey, + deprecatedSecretNamespaceKey: controllerPublishSecretNamespaceKey, + secretNameKey: prefixedControllerPublishSecretNameKey, + secretNamespaceKey: prefixedControllerPublishSecretNamespaceKey, + } + + NodeStageSecretParams = SecretParamsMap{ + name: "NodeStage", + deprecatedSecretNameKey: nodeStageSecretNameKey, + deprecatedSecretNamespaceKey: nodeStageSecretNamespaceKey, + secretNameKey: prefixedNodeStageSecretNameKey, + secretNamespaceKey: prefixedNodeStageSecretNamespaceKey, + } + + ControllerExpandSecretParams = SecretParamsMap{ + name: "ControllerExpand", + secretNameKey: prefixedControllerExpandSecretNameKey, + secretNamespaceKey: prefixedControllerExpandSecretNamespaceKey, + } +) + +// GetVolumeSecretReference returns a reference to the secret specified in the given nameTemplate +// and namespaceTemplate, or an error if the templates are not specified correctly. +// no lookup of the referenced secret is performed, and the secret may or may not exist. +// +// supported tokens for name resolution: +// - ${pv.name} +// - ${pvc.namespace} +// - ${pvc.name} +// - ${pvc.annotations['ANNOTATION_KEY']} (e.g. ${pvc.annotations['example.com/node-publish-secret-name']}) +// +// supported tokens for namespace resolution: +// - ${pv.name} +// - ${pvc.namespace} +// +// an error is returned in the following situations: +// - the nameTemplate or namespaceTemplate contains a token that cannot be resolved +// - the resolved name is not a valid secret name +// - the resolved namespace is not a valid namespace name +func GetVolumeSecretReference(secretParams SecretParamsMap, templateParams map[string]string, pvName string, pvc *v1.PersistentVolumeClaim) (*v1.SecretReference, error) { + nameTemplate, namespaceTemplate, err := VerifyAndGetSecretNameAndNamespaceTemplate(secretParams, templateParams) + if err != nil { + return nil, fmt.Errorf("failed to get name and namespace template from params: %v", err) + } + + // if didn't find secrets for specific call, try to check default values + if nameTemplate == "" && namespaceTemplate == "" { + nameTemplate, namespaceTemplate, err = VerifyAndGetSecretNameAndNamespaceTemplate(DefaultSecretParams, templateParams) + if err != nil { + return nil, fmt.Errorf("failed to get default name and namespace template from params: %v", err) + } + } + + if nameTemplate == "" && namespaceTemplate == "" { + return nil, nil + } + + ref := &v1.SecretReference{} + { + // Secret namespace template can make use of the PV name or the PVC namespace. + // Note that neither of those things are under the control of the PVC user. + namespaceParams := map[string]string{tokenPVNameKey: pvName} + if pvc != nil { + namespaceParams[tokenPVCNameSpaceKey] = pvc.Namespace + } + + resolvedNamespace, err := resolveTemplate(namespaceTemplate, namespaceParams) + if err != nil { + return nil, fmt.Errorf("error resolving value %q: %v", namespaceTemplate, err) + } + if len(validation.IsDNS1123Label(resolvedNamespace)) > 0 { + if namespaceTemplate != resolvedNamespace { + return nil, fmt.Errorf("%q resolved to %q which is not a valid namespace name", namespaceTemplate, resolvedNamespace) + } + return nil, fmt.Errorf("%q is not a valid namespace name", namespaceTemplate) + } + ref.Namespace = resolvedNamespace + } + + { + // Secret name template can make use of the PV name, PVC name or namespace, or a PVC annotation. + // Note that PVC name and annotations are under the PVC user's control. + nameParams := map[string]string{tokenPVNameKey: pvName} + if pvc != nil { + nameParams[tokenPVCNameKey] = pvc.Name + nameParams[tokenPVCNameSpaceKey] = pvc.Namespace + for k, v := range pvc.Annotations { + nameParams["pvc.annotations['"+k+"']"] = v + } + } + resolvedName, err := resolveTemplate(nameTemplate, nameParams) + if err != nil { + return nil, fmt.Errorf("error resolving value %q: %v", nameTemplate, err) + } + if len(validation.IsDNS1123Subdomain(resolvedName)) > 0 { + if nameTemplate != resolvedName { + return nil, fmt.Errorf("%q resolved to %q which is not a valid secret name", nameTemplate, resolvedName) + } + return nil, fmt.Errorf("%q is not a valid secret name", nameTemplate) + } + ref.Name = resolvedName + } + + return ref, nil +} + +func RemovePrefixedVolumeParameters(param map[string]string) (map[string]string, error) { + newParam := map[string]string{} + for k, v := range param { + if strings.HasPrefix(k, csiParameterPrefix) { + // Check if its well known + switch k { + case PrefixedFsTypeKey: + case prefixedProvisionerSecretNameKey: + case prefixedProvisionerSecretNamespaceKey: + case prefixedControllerPublishSecretNameKey: + case prefixedControllerPublishSecretNamespaceKey: + case prefixedNodeStageSecretNameKey: + case prefixedNodeStageSecretNamespaceKey: + case prefixedNodePublishSecretNameKey: + case prefixedNodePublishSecretNamespaceKey: + case prefixedControllerExpandSecretNameKey: + case prefixedControllerExpandSecretNamespaceKey: + case prefixedDefaultSecretNameKey: + case prefixedDefaultSecretNamespaceKey: + default: + return map[string]string{}, fmt.Errorf("found unknown parameter key \"%s\" with reserved namespace %s", k, csiParameterPrefix) + } + } else { + // Don't strip, add this key-value to new map + // Deprecated parameters prefixed with "csi" are not stripped to preserve backwards compatibility + newParam[k] = v + } + } + return newParam, nil +} diff --git a/params/volume_test.go b/params/volume_test.go new file mode 100644 index 00000000..366cdfd1 --- /dev/null +++ b/params/volume_test.go @@ -0,0 +1,362 @@ +/* +Copyright 2021 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 params + +import ( + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestStripPrefixedCSIParams(t *testing.T) { + testcases := []struct { + name string + params map[string]string + expectedParams map[string]string + expectErr bool + }{ + { + name: "no prefix", + params: map[string]string{"csiFoo": "bar", "bim": "baz"}, + expectedParams: map[string]string{"csiFoo": "bar", "bim": "baz"}, + }, + { + name: "one prefixed", + params: map[string]string{prefixedControllerPublishSecretNameKey: "bar", "bim": "baz"}, + expectedParams: map[string]string{"bim": "baz"}, + }, + { + name: "prefix in value", + params: map[string]string{"foo": PrefixedFsTypeKey, "bim": "baz"}, + expectedParams: map[string]string{"foo": PrefixedFsTypeKey, "bim": "baz"}, + }, + { + name: "all known prefixed", + params: map[string]string{ + PrefixedFsTypeKey: "csiBar", + prefixedProvisionerSecretNameKey: "csiBar", + prefixedProvisionerSecretNamespaceKey: "csiBar", + prefixedControllerPublishSecretNameKey: "csiBar", + prefixedControllerPublishSecretNamespaceKey: "csiBar", + prefixedNodeStageSecretNameKey: "csiBar", + prefixedNodeStageSecretNamespaceKey: "csiBar", + prefixedNodePublishSecretNameKey: "csiBar", + prefixedNodePublishSecretNamespaceKey: "csiBar", + prefixedControllerExpandSecretNameKey: "csiBar", + prefixedControllerExpandSecretNamespaceKey: "csiBar", + prefixedDefaultSecretNameKey: "csiBar", + prefixedDefaultSecretNamespaceKey: "csiBar", + }, + expectedParams: map[string]string{}, + }, + { + name: "all known deprecated params not stripped", + params: map[string]string{ + "fstype": "csiBar", + provisionerSecretNameKey: "csiBar", + provisionerSecretNamespaceKey: "csiBar", + controllerPublishSecretNameKey: "csiBar", + controllerPublishSecretNamespaceKey: "csiBar", + nodeStageSecretNameKey: "csiBar", + nodeStageSecretNamespaceKey: "csiBar", + nodePublishSecretNameKey: "csiBar", + nodePublishSecretNamespaceKey: "csiBar", + }, + expectedParams: map[string]string{ + "fstype": "csiBar", + provisionerSecretNameKey: "csiBar", + provisionerSecretNamespaceKey: "csiBar", + controllerPublishSecretNameKey: "csiBar", + controllerPublishSecretNamespaceKey: "csiBar", + nodeStageSecretNameKey: "csiBar", + nodeStageSecretNamespaceKey: "csiBar", + nodePublishSecretNameKey: "csiBar", + nodePublishSecretNamespaceKey: "csiBar", + }, + }, + + { + name: "unknown prefixed var", + params: map[string]string{csiParameterPrefix + "bim": "baz"}, + expectErr: true, + }, + { + name: "empty", + params: map[string]string{}, + expectedParams: map[string]string{}, + }, + } + + for _, tc := range testcases { + t.Logf("test: %v", tc.name) + + newParams, err := RemovePrefixedVolumeParameters(tc.params) + if err != nil { + if tc.expectErr { + continue + } else { + t.Fatalf("Encountered unexpected error: %v", err) + } + } else { + if tc.expectErr { + t.Fatalf("Did not get error when one was expected") + } + } + + eq := reflect.DeepEqual(newParams, tc.expectedParams) + if !eq { + t.Fatalf("Stripped parameters: %v not equal to expected parameters: %v", newParams, tc.expectedParams) + } + } +} + +func TestGetVolumeSecretReference(t *testing.T) { + testcases := map[string]struct { + secretParams SecretParamsMap + params map[string]string + pvName string + pvc *v1.PersistentVolumeClaim + + expectRef *v1.SecretReference + expectErr bool + }{ + "no params": { + secretParams: NodePublishSecretParams, + params: nil, + expectRef: nil, + }, + "empty err": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "", nodePublishSecretNamespaceKey: ""}, + expectErr: true, + }, + "[deprecated] name, no namespace": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "foo"}, + expectErr: true, + }, + "name, no namespace": { + secretParams: NodePublishSecretParams, + params: map[string]string{prefixedNodePublishSecretNameKey: "foo"}, + expectErr: true, + }, + "[deprecated] namespace, no name": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNamespaceKey: "foo"}, + expectErr: true, + }, + "namespace, no name": { + secretParams: NodePublishSecretParams, + params: map[string]string{prefixedNodePublishSecretNamespaceKey: "foo"}, + expectErr: true, + }, + "[deprecated] simple - valid": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "name", nodePublishSecretNamespaceKey: "ns"}, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"}, + }, + "deprecated and new both": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "name", nodePublishSecretNamespaceKey: "ns", prefixedNodePublishSecretNameKey: "name", prefixedNodePublishSecretNamespaceKey: "ns"}, + expectErr: true, + }, + "deprecated and new names": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "name", nodePublishSecretNamespaceKey: "ns", prefixedNodePublishSecretNameKey: "name"}, + expectErr: true, + }, + "deprecated and new namespace": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "name", nodePublishSecretNamespaceKey: "ns", prefixedNodePublishSecretNamespaceKey: "ns"}, + expectErr: true, + }, + "deprecated and new mixed": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "name", prefixedNodePublishSecretNamespaceKey: "ns"}, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"}, + }, + "simple - valid": { + secretParams: NodePublishSecretParams, + params: map[string]string{prefixedNodePublishSecretNameKey: "name", prefixedNodePublishSecretNamespaceKey: "ns"}, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"}, + }, + "simple - valid, no pvc": { + secretParams: ProvisionerSecretParams, + params: map[string]string{provisionerSecretNameKey: "name", provisionerSecretNamespaceKey: "ns"}, + pvc: nil, + expectRef: &v1.SecretReference{Name: "name", Namespace: "ns"}, + }, + "simple - valid, pvc name and namespace": { + secretParams: ProvisionerSecretParams, + params: map[string]string{ + provisionerSecretNameKey: "param-name", + provisionerSecretNamespaceKey: "param-ns", + }, + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "ns", + }, + }, + expectRef: &v1.SecretReference{Name: "param-name", Namespace: "param-ns"}, + }, + "simple - invalid name": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "bad name", nodePublishSecretNamespaceKey: "ns"}, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: nil, + expectErr: true, + }, + "simple - invalid namespace": { + secretParams: NodePublishSecretParams, + params: map[string]string{nodePublishSecretNameKey: "name", nodePublishSecretNamespaceKey: "bad ns"}, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: nil, + expectErr: true, + }, + "template - PVC name annotations not supported for Provision and Delete": { + secretParams: ProvisionerSecretParams, + params: map[string]string{ + prefixedProvisionerSecretNameKey: "static-${pv.name}-${pvc.namespace}-${pvc.name}-${pvc.annotations['akey']}", + }, + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "ns", + }, + }, + expectErr: true, + }, + "template - valid nodepublish secret ref": { + secretParams: NodePublishSecretParams, + params: map[string]string{ + nodePublishSecretNameKey: "static-${pv.name}-${pvc.namespace}-${pvc.name}-${pvc.annotations['akey']}", + nodePublishSecretNamespaceKey: "static-${pv.name}-${pvc.namespace}", + }, + pvName: "pvname", + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvcname", + Namespace: "pvcnamespace", + Annotations: map[string]string{"akey": "avalue"}, + }, + }, + expectRef: &v1.SecretReference{Name: "static-pvname-pvcnamespace-pvcname-avalue", Namespace: "static-pvname-pvcnamespace"}, + }, + "template - valid provisioner secret ref": { + secretParams: ProvisionerSecretParams, + params: map[string]string{ + provisionerSecretNameKey: "static-provisioner-${pv.name}-${pvc.namespace}-${pvc.name}", + provisionerSecretNamespaceKey: "static-provisioner-${pv.name}-${pvc.namespace}", + }, + pvName: "pvname", + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvcname", + Namespace: "pvcnamespace", + }, + }, + expectRef: &v1.SecretReference{Name: "static-provisioner-pvname-pvcnamespace-pvcname", Namespace: "static-provisioner-pvname-pvcnamespace"}, + }, + "template - valid, with pvc.name": { + secretParams: ProvisionerSecretParams, + params: map[string]string{ + provisionerSecretNameKey: "${pvc.name}", + provisionerSecretNamespaceKey: "ns", + }, + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvcname", + Namespace: "pvcns", + }, + }, + expectRef: &v1.SecretReference{Name: "pvcname", Namespace: "ns"}, + }, + "template - valid, provisioner with pvc name and namepsace": { + secretParams: ProvisionerSecretParams, + params: map[string]string{ + provisionerSecretNameKey: "${pvc.name}", + provisionerSecretNamespaceKey: "${pvc.namespace}", + }, + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvcname", + Namespace: "pvcns", + }, + }, + expectRef: &v1.SecretReference{Name: "pvcname", Namespace: "pvcns"}, + }, + "template - valid, static pvc name and templated namespace": { + secretParams: ProvisionerSecretParams, + params: map[string]string{ + provisionerSecretNameKey: "static-name-1", + provisionerSecretNamespaceKey: "${pvc.namespace}", + }, + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "ns", + }, + }, + expectRef: &v1.SecretReference{Name: "static-name-1", Namespace: "ns"}, + }, + "template - invalid namespace tokens": { + secretParams: NodePublishSecretParams, + params: map[string]string{ + nodePublishSecretNameKey: "myname", + nodePublishSecretNamespaceKey: "mynamespace${bar}", + }, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: nil, + expectErr: true, + }, + "template - invalid name tokens": { + secretParams: NodePublishSecretParams, + params: map[string]string{ + nodePublishSecretNameKey: "myname${foo}", + nodePublishSecretNamespaceKey: "mynamespace", + }, + pvc: &v1.PersistentVolumeClaim{}, + expectRef: nil, + expectErr: true, + }, + } + + for k, tc := range testcases { + t.Run(k, func(t *testing.T) { + ref, err := GetVolumeSecretReference(tc.secretParams, tc.params, tc.pvName, tc.pvc) + if err != nil { + if tc.expectErr { + return + } + t.Fatalf("Did not expect error but got: %v", err) + } else { + if tc.expectErr { + t.Fatalf("Expected error but got none") + } + } + if !reflect.DeepEqual(ref, tc.expectRef) { + t.Errorf("Expected %v, got %v", tc.expectRef, ref) + } + }) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 423b0b3e..9bf417b0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -238,6 +238,7 @@ k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 # k8s.io/apimachinery v0.22.0-beta.1 => k8s.io/apimachinery v0.22.0-beta.1 +## explicit k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/meta k8s.io/apimachinery/pkg/api/resource