diff --git a/Makefile b/Makefile index 18e62a79a..727b1674b 100644 --- a/Makefile +++ b/Makefile @@ -287,15 +287,15 @@ endif .PHONY: e2e-provider-container e2e-provider-container: - docker buildx build --no-cache -t $(E2E_PROVIDER_IMAGE_TAG) -f test/e2eprovider/Dockerfile --progress=plain . + docker buildx build --no-cache --output=type=docker -t $(E2E_PROVIDER_IMAGE_TAG) -f test/e2eprovider/Dockerfile --progress=plain . .PHONY: container container: crd-container - docker buildx build --no-cache --build-arg IMAGE_VERSION=$(IMAGE_VERSION) -t $(IMAGE_TAG) -f docker/Dockerfile --progress=plain . + docker buildx build --no-cache --output=type=docker --build-arg IMAGE_VERSION=$(IMAGE_VERSION) -t $(IMAGE_TAG) -f docker/Dockerfile --progress=plain . .PHONY: crd-container crd-container: build-crds - docker buildx build --no-cache -t $(CRD_IMAGE_TAG) -f docker/crd.Dockerfile --progress=plain _output/crds/ + docker buildx build --no-cache --output=type=docker -t $(CRD_IMAGE_TAG) -f docker/crd.Dockerfile --progress=plain _output/crds/ .PHONY: crd-container-linux crd-container-linux: build-crds docker-buildx-builder @@ -462,7 +462,7 @@ e2e-eks-cleanup: .PHONY: e2e-provider e2e-provider: - bats -t -T test/bats/e2e-provider.bats + bats -t test/bats/e2e-provider.bats .PHONY: e2e-azure e2e-azure: $(AZURE_CLI) diff --git a/apis/v1/secretproviderclass_types.go b/apis/v1/secretproviderclass_types.go index 4645c8b0a..922ccda02 100644 --- a/apis/v1/secretproviderclass_types.go +++ b/apis/v1/secretproviderclass_types.go @@ -44,6 +44,15 @@ type SecretObject struct { // annotations of k8s secret object Annotations map[string]string `json:"annotations,omitempty"` Data []*SecretObjectData `json:"data,omitempty"` + // SyncAll syncs all secrets defined in the parameters field of SecretProviderClass + SyncAll bool `json:"syncAll,omitempty"` +} + +type SyncOptions struct { + // syncs all secrets listed in the parameters field of SecretProviderClass + SyncAll bool `json:"syncAll,omitempty"` + // type of K8s secret object + Type string `json:"type,omitempty"` } // SecretProviderClassSpec defines the desired state of SecretProviderClass @@ -53,6 +62,7 @@ type SecretProviderClassSpec struct { // Configuration for specific provider Parameters map[string]string `json:"parameters,omitempty"` SecretObjects []*SecretObject `json:"secretObjects,omitempty"` + SyncOptions SyncOptions `json:"syncOptions,omitempty"` } // ByPodStatus defines the state of SecretProviderClass as seen by diff --git a/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml b/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml index 9a3bf5022..c8a5e0a7b 100644 --- a/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml +++ b/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml @@ -1,4 +1,3 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -16,178 +15,203 @@ spec: singular: secretproviderclass scope: Namespaced versions: - - name: v1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + - name: v1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider + type: object + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects + syncOptions: + description: SyncOptions defines the secret type when syncing all secrets listed in the parameters field properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string - type: object - type: array - labels: - additionalProperties: - type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string + syncAll: + description: SyncAll as true will sync all secrets defined in the parameters field to K8s + type: boolean type: description: type of K8s secret object type: string type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + syncAll: + description: SyncAll can sync all secrets defined in the parameters field of SecretProviderClass + type: boolean + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object + type: object + secretName: + description: name of the K8s secret object + type: string + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider type: object - type: array - type: object - type: object - served: true - storage: true - - name: v1alpha1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects - properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object type: object - type: array - labels: - additionalProperties: + secretName: + description: name of the K8s secret object type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string - type: - description: type of K8s secret object - type: string - type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string - type: object - type: array - type: object - type: object - served: true - storage: false + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" diff --git a/config/crd/bases/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml b/config/crd/bases/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml index 9a3bf5022..c8a5e0a7b 100644 --- a/config/crd/bases/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml +++ b/config/crd/bases/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml @@ -1,4 +1,3 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -16,178 +15,203 @@ spec: singular: secretproviderclass scope: Namespaced versions: - - name: v1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + - name: v1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider + type: object + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects + syncOptions: + description: SyncOptions defines the secret type when syncing all secrets listed in the parameters field properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string - type: object - type: array - labels: - additionalProperties: - type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string + syncAll: + description: SyncAll as true will sync all secrets defined in the parameters field to K8s + type: boolean type: description: type of K8s secret object type: string type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + syncAll: + description: SyncAll can sync all secrets defined in the parameters field of SecretProviderClass + type: boolean + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object + type: object + secretName: + description: name of the K8s secret object + type: string + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider type: object - type: array - type: object - type: object - served: true - storage: true - - name: v1alpha1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects - properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object type: object - type: array - labels: - additionalProperties: + secretName: + description: name of the K8s secret object type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string - type: - description: type of K8s secret object - type: string - type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string - type: object - type: array - type: object - type: object - served: true - storage: false + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" diff --git a/controllers/secretproviderclasspodstatus_controller.go b/controllers/secretproviderclasspodstatus_controller.go index c327459fa..a873cd82b 100644 --- a/controllers/secretproviderclasspodstatus_controller.go +++ b/controllers/secretproviderclasspodstatus_controller.go @@ -45,6 +45,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/secrets-store-csi-driver/pkg/util/spcutil" ) const ( @@ -115,7 +116,7 @@ func (r *SecretProviderClassPodStatusReconciler) Patcher(ctx context.Context) er } spcPodStatuses := spcPodStatusList.Items - for i := range spcPodStatuses { + for i, spcPodStatus := range spcPodStatuses { spcName := spcPodStatuses[i].Status.SecretProviderClassName spc := &secretsstorev1.SecretProviderClass{} namespace := spcPodStatuses[i].Namespace @@ -161,6 +162,19 @@ func (r *SecretProviderClassPodStatusReconciler) Patcher(ctx context.Context) er ownerRefs = append(ownerRefs, ref) } + if spc.Spec.SyncOptions.SyncAll { + files, err := fileutil.GetMountedFiles(spcPodStatus.Status.TargetPath) + if err != nil { + return fmt.Errorf("failed to get mounted files for pod %s/%s: %v", namespace, pod.Name, err) + } else { + if len(spc.Spec.SecretObjects) == 0 { + spc.Spec.SecretObjects = spcutil.BuildSecretObjects(files, secretutil.GetSecretType(strings.TrimSpace(spc.Spec.SyncOptions.Type))) + } else { + spc.Spec.SecretObjects = append(spc.Spec.SecretObjects, spcutil.BuildSecretObjects(files, secretutil.GetSecretType(strings.TrimSpace(spc.Spec.SyncOptions.Type)))...) + } + } + } + for _, secret := range spc.Spec.SecretObjects { key := types.NamespacedName{Name: secret.SecretName, Namespace: namespace} val, exists := secretOwnerMap[key] @@ -258,7 +272,7 @@ func (r *SecretProviderClassPodStatusReconciler) Reconcile(ctx context.Context, return ctrl.Result{}, err } - if len(spc.Spec.SecretObjects) == 0 { + if len(spc.Spec.SecretObjects) == 0 && !spc.Spec.SyncOptions.SyncAll { klog.InfoS("no secret objects defined for spc, nothing to reconcile", "spc", klog.KObj(spc), "spcps", klog.KObj(spcPodStatus)) return ctrl.Result{}, nil } @@ -283,6 +297,25 @@ func (r *SecretProviderClassPodStatusReconciler) Reconcile(ctx context.Context, klog.ErrorS(err, "failed to get mounted files", "spc", klog.KObj(spc), "pod", klog.KObj(pod), "spcps", klog.KObj(spcPodStatus)) return ctrl.Result{RequeueAfter: 10 * time.Second}, err } + + if spc.Spec.SyncOptions.SyncAll { + if len(spc.Spec.SecretObjects) == 0 { + spc.Spec.SecretObjects = spcutil.BuildSecretObjects(files, secretutil.GetSecretType(strings.TrimSpace(spc.Spec.SyncOptions.Type))) + } else { + spc.Spec.SecretObjects = append(spc.Spec.SecretObjects, spcutil.BuildSecretObjects(files, secretutil.GetSecretType(strings.TrimSpace(spc.Spec.SyncOptions.Type)))...) + } + } + + for _, secretObj := range spc.Spec.SecretObjects { + if secretObj.SyncAll { + if secretutil.GetSecretType(strings.TrimSpace(secretObj.Type)) != corev1.SecretTypeOpaque { + return ctrl.Result{}, fmt.Errorf("secret provider class %s/%s cannot use secretObjects[*].syncAll for non-opaque secrets", spc.Namespace, spc.Name) + } + + spcutil.BuildSecretObjectData(files, secretObj) + } + } + errs := make([]error, 0) for _, secretObj := range spc.Spec.SecretObjects { secretName := strings.TrimSpace(secretObj.SecretName) diff --git a/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml b/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml index 9a3bf5022..c8a5e0a7b 100644 --- a/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml +++ b/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml @@ -1,4 +1,3 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -16,178 +15,203 @@ spec: singular: secretproviderclass scope: Namespaced versions: - - name: v1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + - name: v1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider + type: object + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects + syncOptions: + description: SyncOptions defines the secret type when syncing all secrets listed in the parameters field properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string - type: object - type: array - labels: - additionalProperties: - type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string + syncAll: + description: SyncAll as true will sync all secrets defined in the parameters field to K8s + type: boolean type: description: type of K8s secret object type: string type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + syncAll: + description: SyncAll can sync all secrets defined in the parameters field of SecretProviderClass + type: boolean + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object + type: object + secretName: + description: name of the K8s secret object + type: string + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider type: object - type: array - type: object - type: object - served: true - storage: true - - name: v1alpha1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects - properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object type: object - type: array - labels: - additionalProperties: + secretName: + description: name of the K8s secret object type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string - type: - description: type of K8s secret object - type: string - type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string - type: object - type: array - type: object - type: object - served: true - storage: false + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" diff --git a/docs/book/src/topics/sync-all-secrets-to-k8s.md b/docs/book/src/topics/sync-all-secrets-to-k8s.md new file mode 100644 index 000000000..fb252f2a5 --- /dev/null +++ b/docs/book/src/topics/sync-all-secrets-to-k8s.md @@ -0,0 +1,496 @@ +# Sync All Secrets to K8s + +
+Opaque Examples + +- In this example, we can expect the secrets that are mounted on the paths `/mnt/secrets-store/username` and `/mnt/secrets-store/password` to be synced to K8s individually. +- The value of `objectName` is the name of the K8s secret + + +```yaml +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: vault-opaque +spec: + provider: vault + parameters: + roleName: "csi" + vaultAddress: "http://vault.vault:8200" + objects: | + - secretPath: "secret/data/db-creds" + objectName: "username" + secretKey: "username" + - secretPath: "secret/data/db-creds" + objectName: "password" + secretKey: "password" + syncOptions: + syncAll: true + type: Opaque +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: opaque-sa +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox-deployment-opaque + labels: + app: busybox +spec: + replicas: 1 + selector: + matchLabels: + app: busybox + template: + metadata: + labels: + app: busybox + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: opaque-sa + containers: + - image: k8s.gcr.io/e2e-test-images/busybox:1.29 + name: busybox + imagePullPolicy: IfNotPresent + command: + - "/bin/sleep" + - "10000" + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: username + key: username + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: password + key: password + volumeMounts: + - name: secrets-store-inline + mountPath: "/mnt/secrets-store" + readOnly: true + volumes: + - name: secrets-store-inline + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "vault-opaque" + +``` + +- In this example, we are nesting some secrets further into the filesystem. Since K8s secrets must have unique names, the name of the synced secret will be a hyphen-separated version of the `objectName` value. +- In this case, we can expect the following secrets in K8s +1. username +2. password +3. nested-username +4. nested-password + +- You will also notice that we have one item in the `.spec.secretObjects` field. With `Opaque` type secrets, we can sync all mounted secrets into a single K8s secret. **This does not work with other secret types** +- In this example, we can expect a K8s secret named `db-secret` with the following keys +1. username +2. password +3. nested-username +4. nested-password + +```yaml +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: vault-opaque +spec: + provider: vault + parameters: + roleName: "csi" + vaultAddress: "http://vault.vault:8200" + objects: | + - secretPath: "secret/data/db-creds" + objectName: "username" + secretKey: "username" + - secretPath: "secret/data/db-creds" + objectName: "password" + secretKey: "password" + - secretPath: "secret/data/db-creds" + objectName: "nested/username" + secretKey: "username" + - secretPath: "secret/data/db-creds" + objectName: "nested/password" + secretKey: "password" + syncOptions: + type: Opaque + syncAll: true + secretObjects: + - secretName: db-secret + type: Opaque + syncAll: true +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: opaque-sa +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox-deployment-opaque + labels: + app: busybox +spec: + replicas: 1 + selector: + matchLabels: + app: busybox + template: + metadata: + labels: + app: busybox + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: opaque-sa + containers: + - image: k8s.gcr.io/e2e-test-images/busybox:1.29 + name: busybox + imagePullPolicy: IfNotPresent + command: + - "/bin/sleep" + - "10000" + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: username + key: username + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: password + key: password + - name: DB_NESTED_USER + valueFrom: + secretKeyRef: + name: nested-username + key: username + - name: DB_NESTED_PASSWORD + valueFrom: + secretKeyRef: + name: nested-password + key: password + - name: DB_SECRET_USER + valueFrom: + secretKeyRef: + name: db-secret + key: username + - name: DB_SECRET_PASSWORD + valueFrom: + secretKeyRef: + name: db-secret + key: password + - name: DB_SECRET_NESTED_USER + valueFrom: + secretKeyRef: + name: db-secret + key: username + - name: DB_SECRET_NESTED_PASSWORD + valueFrom: + secretKeyRef: + name: db-secret + key: password + volumeMounts: + - name: secrets-store-inline + mountPath: "/mnt/secrets-store" + readOnly: true + volumes: + - name: secrets-store-inline + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "vault-opaque" + +``` + +
+ +
+TLS Examples + +- For TLS secrets, you need to mount the certificate and private key on a single mount as shown in the example below. +- The driver will separate the two values and assign them to the `tls.crt` and `tls.key` keys respectively. + +```yaml +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: vault-tls +spec: + provider: vault + parameters: + roleName: "csi" + vaultAddress: "http://vault.vault:8200" + objects: | + - secretPath: "secret/data/certs" + objectName: "cert1" + secretKey: "cert1" + - secretPath: "secret/data/certs" + objectName: "cert2" + secretKey: "cert2" + syncOptions: + syncAll: true + type: kubernetes.io/tls +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: tls-sa +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox-deployment-tls + labels: + app: busybox-tls +spec: + replicas: 1 + selector: + matchLabels: + app: busybox-tls + template: + metadata: + labels: + app: busybox-tls + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: tls-sa + containers: + - image: k8s.gcr.io/e2e-test-images/busybox:1.29 + name: busybox-tls + imagePullPolicy: IfNotPresent + command: + - "/bin/sleep" + - "10000" + env: + - name: PRIVATE_KEY_1 + valueFrom: + secretKeyRef: + name: cert1 + key: tls.key + - name: CERTIFICATE_1 + valueFrom: + secretKeyRef: + name: cert1 + key: tls.crt + - name: PRIVATE_KEY_2 + valueFrom: + secretKeyRef: + name: cert2 + key: tls.key + - name: CERTIFICATE_2 + valueFrom: + secretKeyRef: + name: cert2 + key: tls.crt + volumeMounts: + - name: secrets-store-inline + mountPath: "/mnt/secrets-store" + readOnly: true + volumes: + - name: secrets-store-inline + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "vault-tls" +``` + +```txt +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgIJAP0J5Z7N0Y5fMA0GCSqGSIb3DQEBCwUAMDMxFzAVBgNV +BAMMDmRlbW8uYXp1cmUuY29tMRgwFgYDVQQKDA9ha3MtaW5ncmVzcy10bHMwHhcN +MjAwNDE1MDQyMzQ2WhcNMjEwNDE1MDQyMzQ2WjAzMRcwFQYDVQQDDA5kZW1vLmF6 +dXJlLmNvbTEYMBYGA1UECgwPYWtzLWluZ3Jlc3MtdGxzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAyS3Zky3n8JlLBxPLzgUpKZYxvzRadeWLmWVbK9by +o08S0Ss8Jao7Ay1wHtnLbn52rzCX6IX1sAe1TAT755Gk7JtLMkshtj6F8BNeelEy +E1gsBE5ntY5vyLTm/jZUIKz2Z9TLnqvQTmp6gJ68BKJ1NobnsHiAcKc6hI7kmY9C +oshmAi5qiKYBgzv/thji0093vtVSa9iwHhQp+AEIMhkvM5ZZkiU5eE6MT9SBEcVW +KmWF28UsB04daYwS2MKJ5l6d4n0LUdAG0FBt1lCoT9rwUDj9l3Mqmi953gw26LUr +NrYnM/8N2jl7Cuyw5alIWaUDrt5i+pu8wdWfzVk+fO7x8QIDAQABo1AwTjAdBgNV +HQ4EFgQUwFBbR014McETdrGGklpEQcl71Q0wHwYDVR0jBBgwFoAUwFBbR014McET +drGGklpEQcl71Q0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATgTy +gg1Q6ISSekiBCe12dqUTMFQh9GKpfYWKRbMtjOjpc7Mdwkdmm3Fu6l3RfEFT28Ij +fy97LMYv8W7beemDFqdmneb2w2ww0ZAFJg+GqIJZ9s/JadiFBDNU7CmJMhA225Qz +XC8ovejiePslnL4QJWlhVG93ZlBJ6SDkRgfcoIW2x4IBE6wv7jmRF4lOvb3z1ddP +iPQqhbEEbwMpXmWv7/2RnjAHdjdGaWRMC5+CaI+lqHyj6ir1c+e6u1QUY54qjmgM +koN/frqYab5Ek3kauj1iqW7rPkrFCqT2evh0YRqb1bFsCLJrRNxnOZ5wKXV/OYQa +QX5t0wFGCZ0KlbXDiw== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJLdmTLefwmUsH +E8vOBSkpljG/NFp15YuZZVsr1vKjTxLRKzwlqjsDLXAe2ctufnavMJfohfWwB7VM +BPvnkaTsm0sySyG2PoXwE156UTITWCwETme1jm/ItOb+NlQgrPZn1Mueq9BOanqA +nrwEonU2hueweIBwpzqEjuSZj0KiyGYCLmqIpgGDO/+2GOLTT3e+1VJr2LAeFCn4 +AQgyGS8zllmSJTl4ToxP1IERxVYqZYXbxSwHTh1pjBLYwonmXp3ifQtR0AbQUG3W +UKhP2vBQOP2XcyqaL3neDDbotSs2ticz/w3aOXsK7LDlqUhZpQOu3mL6m7zB1Z/N +WT587vHxAgMBAAECggEAJb0qIYftCJ9ZCbzW8JDbRefc8SdbCN7Er0PqNHEgFy6Q +MxjPMambZF8ztzXYCaRDk12kQYRPsHPhuJ7+ulQCAjinhIm/izZzXbPkd0GgCSzz +JOOoZNCRe68j3fBHG9IWbyfmAp/sdalXzaT5VE09e7sW323bekaEnbVIgN30/CAS +gI77YdaIhG+PT/pSCOc11MTkBJp+VhT1tEtlRAR78b1RXbGi1oUHRee7C3Ia8IKQ +3L5dPxR9RsYsR2O66908kEi8ZcuIjcbIuRPDXYHY+5Nwm3mXuZlkyjyfxJXsIA8i +qBrQrSpHGgAn1TVlLDSCKPLbkRzBRRvAW0zL/cDTuQKBgQDq/9Yxx9QivAuUxxdE +u0VO5CzzZYFWhDxAXS3/wYyo1YnoPtUz/lGCvMWp0k2aaa0+KTXv2fRCUGSujHW7 +Jfo4kuMPkauAhoXx9QJAcjoK0nNbYEaqoJyMoRID+Qb9XHkj+lmBTmMVgALCT9DI +HekHj/M3b7CknbfWv1sOZ/vpQwKBgQDbKEuP/DWQa9DC5nn5phHD/LWZLG/cMR4X +TmwM/cbfRxM/6W0+/KLAodz4amGRzVlW6ax4k26BSE8Zt/SiyA1DQRTeFloduoqW +iWF4dMeItxw2am+xLREwtoN3FgsJHu2z/O/0aaBAOMLUXIPIyiE4L6OnEPifE/pb +AM8EbM5auwKBgGhdABIRjbtzSa1kEYhbprcXjIL3lE4I4f0vpIsNuNsOInW62dKC +Yk6uaRY3KHGn9uFBSgvf/qMost310R8xCYPwb9htN/4XQAspZTubvv0pY0O0aQ3D +0GJ/8dFD2f/Q/pekyfUsC8Lzm8YRzkXhSqkqG7iF6Kviw08iolyuf2ijAoGBANaA +pRzDvWWisUziKsa3zbGnGdNXVBEPniUvo8A/b7RAK84lWcEJov6qLs6RyPfdJrFT +u3S00LcHICzLCU1+QsTt4U/STtfEKjtXMailnFrq5lk4aiPfOXEVYq1fTOPbesrt +Katu6uOQ6tjRyEbx1/vXXPV7Peztr9/8daMeIAdbAoGBAOYRJ1CzMYQKjWF32Uas +7hhQxyH1QI4nV56Dryq7l/UWun2pfwNLZFqOHD3qm05aznzNKvk9aHAsOPFfUUXO +7sp0Ge5FLMSw1uMNnutcVcMz37KAY2fOoE2xoLM4DU/H2NqDjeGCsOsU1ReRS1vB +J+42JGwBdLV99ruYKVKOWPh4 +-----END PRIVATE KEY----- +``` + +
+ +
+Basic Auth Examples + +- Basic Auth secrets require values for the `username` and `password` keys in K8s. +- In your secret store of choice, these values should be comma-separated as a single value - `myusername,mypassword`. +- The driver will separate the two values and assign them to the `username` and `password` keys respectively. + +```yaml +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: vault-basic +spec: + provider: vault + parameters: + roleName: "csi" + vaultAddress: "http://vault.vault:8200" + objects: | + - secretPath: "secret/data/basic1" + objectName: "basic/basic1" + secretKey: "credentials" + - secretPath: "secret/data/basic2" + objectName: "basic/basic2" + secretKey: "credentials" + - secretPath: "secret/data/basic3" + objectName: "basic/basic3" + secretKey: "credentials" + syncOptions: + syncAll: true + type: kubernetes.io/basic-auth +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: basic-sa +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox-deployment-basic + labels: + app: busybox-basic +spec: + replicas: 1 + selector: + matchLabels: + app: busybox-basic + template: + metadata: + labels: + app: busybox-basic + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: basic-sa + containers: + - image: k8s.gcr.io/e2e-test-images/busybox:1.29 + name: busybox-tls + imagePullPolicy: IfNotPresent + command: + - "/bin/sleep" + - "10000" + env: + - name: BASIC_USERNAME_1 + valueFrom: + secretKeyRef: + name: basic-basic1 + key: username + - name: BASIC_PASSWORD_1 + valueFrom: + secretKeyRef: + name: basic-basic1 + key: password + - name: BASIC_USERNAME_2 + valueFrom: + secretKeyRef: + name: basic-basic2 + key: username + - name: BASIC_PASSWORD_2 + valueFrom: + secretKeyRef: + name: basic-basic2 + key: password + - name: BASIC_USERNAME_3 + valueFrom: + secretKeyRef: + name: basic-basic3 + key: username + - name: BASIC_PASSWORD_3 + valueFrom: + secretKeyRef: + name: basic-basic3 + key: password + volumeMounts: + - name: secrets-store-inline + mountPath: "/mnt/secrets-store" + readOnly: true + volumes: + - name: secrets-store-inline + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "vault-basic" + +``` + +
+ +You may know already that you want to sync all your secrets to K8s using the CSI driver, but adding each secret to the SecretProviderClass configuration can be time-consuming and error-prone. In this case, you can tell the driver to sync all mounted secrets to K8s with the **SyncAll** option. + +> NOTE: This feature is only available for secrets-store.csi.x-k8s.io/v1 + +See the full examples above to get a better understanding for the configuration shown: + +```yaml +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: vault-opaque +spec: + provider: vault + parameters: + roleName: "csi" + vaultAddress: "http://vault.vault:8200" + objects: | + - secretPath: "secret/data/db-creds" + objectName: "username" + secretKey: "username" + - secretPath: "secret/data/db-creds" + objectName: "password" + secretKey: "password" + syncOptions: + syncAll: true + type: Opaque +``` \ No newline at end of file diff --git a/manifest_staging/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml b/manifest_staging/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml index 9a3bf5022..c8a5e0a7b 100644 --- a/manifest_staging/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml +++ b/manifest_staging/charts/secrets-store-csi-driver/crds/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml @@ -1,4 +1,3 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -16,178 +15,203 @@ spec: singular: secretproviderclass scope: Namespaced versions: - - name: v1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + - name: v1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider + type: object + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects + syncOptions: + description: SyncOptions defines the secret type when syncing all secrets listed in the parameters field properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string - type: object - type: array - labels: - additionalProperties: - type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string + syncAll: + description: SyncAll as true will sync all secrets defined in the parameters field to K8s + type: boolean type: description: type of K8s secret object type: string type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + syncAll: + description: SyncAll can sync all secrets defined in the parameters field of SecretProviderClass + type: boolean + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object + type: object + secretName: + description: name of the K8s secret object + type: string + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider type: object - type: array - type: object - type: object - served: true - storage: true - - name: v1alpha1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects - properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object type: object - type: array - labels: - additionalProperties: + secretName: + description: name of the K8s secret object type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string - type: - description: type of K8s secret object - type: string - type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string - type: object - type: array - type: object - type: object - served: true - storage: false + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" diff --git a/manifest_staging/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml b/manifest_staging/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml index 9a3bf5022..c8a5e0a7b 100644 --- a/manifest_staging/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml +++ b/manifest_staging/deploy/secrets-store.csi.x-k8s.io_secretproviderclasses.yaml @@ -1,4 +1,3 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -16,178 +15,203 @@ spec: singular: secretproviderclass scope: Namespaced versions: - - name: v1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + - name: v1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider + type: object + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects + syncOptions: + description: SyncOptions defines the secret type when syncing all secrets listed in the parameters field properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string - type: object - type: array - labels: - additionalProperties: - type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string + syncAll: + description: SyncAll as true will sync all secrets defined in the parameters field to K8s + type: boolean type: description: type of K8s secret object type: string type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + syncAll: + description: SyncAll can sync all secrets defined in the parameters field of SecretProviderClass + type: boolean + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object + type: object + secretName: + description: name of the K8s secret object + type: string + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + - name: v1alpha1 + schema: + openAPIV3Schema: + description: + SecretProviderClass is the Schema for the secretproviderclasses + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: SecretProviderClassSpec defines the desired state of SecretProviderClass + properties: + parameters: + additionalProperties: + type: string + description: Configuration for specific provider type: object - type: array - type: object - type: object - served: true - storage: true - - name: v1alpha1 - schema: - openAPIV3Schema: - description: SecretProviderClass is the Schema for the secretproviderclasses - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SecretProviderClassSpec defines the desired state of SecretProviderClass - properties: - parameters: - additionalProperties: + provider: + description: Configuration for provider name type: string - description: Configuration for specific provider - type: object - provider: - description: Configuration for provider name - type: string - secretObjects: - items: - description: SecretObject defines the desired state of synced K8s - secret objects - properties: - annotations: - additionalProperties: - type: string - description: annotations of k8s secret object - type: object - data: - items: - description: SecretObjectData defines the desired state of - synced K8s secret object data - properties: - key: - description: data field to populate - type: string - objectName: - description: name of the object to sync - type: string + secretObjects: + items: + description: + SecretObject defines the desired state of synced K8s + secret objects + properties: + annotations: + additionalProperties: + type: string + description: annotations of k8s secret object + type: object + data: + items: + description: + SecretObjectData defines the desired state of + synced K8s secret object data + properties: + key: + description: data field to populate + type: string + objectName: + description: name of the object to sync + type: string + type: object + type: array + labels: + additionalProperties: + type: string + description: labels of K8s secret object type: object - type: array - labels: - additionalProperties: + secretName: + description: name of the K8s secret object type: string - description: labels of K8s secret object - type: object - secretName: - description: name of the K8s secret object - type: string - type: - description: type of K8s secret object - type: string - type: object - type: array - type: object - status: - description: SecretProviderClassStatus defines the observed state of SecretProviderClass - properties: - byPod: - items: - description: ByPodStatus defines the state of SecretProviderClass - as seen by an individual controller - properties: - id: - description: id of the pod that wrote the status - type: string - namespace: - description: namespace of the pod that wrote the status - type: string - type: object - type: array - type: object - type: object - served: true - storage: false + type: + description: type of K8s secret object + type: string + type: object + type: array + type: object + status: + description: SecretProviderClassStatus defines the observed state of SecretProviderClass + properties: + byPod: + items: + description: + ByPodStatus defines the state of SecretProviderClass + as seen by an individual controller + properties: + id: + description: id of the pod that wrote the status + type: string + namespace: + description: namespace of the pod that wrote the status + type: string + type: object + type: array + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" diff --git a/pkg/rotation/reconciler.go b/pkg/rotation/reconciler.go index 877058a90..9560e68c9 100644 --- a/pkg/rotation/reconciler.go +++ b/pkg/rotation/reconciler.go @@ -34,9 +34,11 @@ import ( "sigs.k8s.io/secrets-store-csi-driver/pkg/util/k8sutil" "sigs.k8s.io/secrets-store-csi-driver/pkg/util/secretutil" "sigs.k8s.io/secrets-store-csi-driver/pkg/util/spcpsutil" + "sigs.k8s.io/secrets-store-csi-driver/pkg/util/spcutil" "sigs.k8s.io/secrets-store-csi-driver/pkg/version" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -429,7 +431,7 @@ func (r *Reconciler) reconcile(ctx context.Context, spcps *secretsstorev1.Secret } } - if len(spc.Spec.SecretObjects) == 0 { + if len(spc.Spec.SecretObjects) == 0 && !spc.Spec.SyncOptions.SyncAll { klog.InfoS("spc doesn't contain secret objects", "spc", klog.KObj(spc), "pod", klog.KObj(pod), "controller", "rotation") return nil } @@ -438,6 +440,25 @@ func (r *Reconciler) reconcile(ctx context.Context, spcps *secretsstorev1.Secret r.generateEvent(pod, corev1.EventTypeWarning, k8sSecretRotationFailedReason, fmt.Sprintf("failed to get mounted files, err: %+v", err)) return fmt.Errorf("failed to get mounted files, err: %w", err) } + + if spc.Spec.SyncOptions.SyncAll { + if len(spc.Spec.SecretObjects) == 0 { + spc.Spec.SecretObjects = spcutil.BuildSecretObjects(files, secretutil.GetSecretType(strings.TrimSpace(spc.Spec.SyncOptions.Type))) + } else { + spc.Spec.SecretObjects = append(spc.Spec.SecretObjects, spcutil.BuildSecretObjects(files, secretutil.GetSecretType(strings.TrimSpace(spc.Spec.SyncOptions.Type)))...) + } + } + + for _, secretObj := range spc.Spec.SecretObjects { + if secretObj.SyncAll { + if secretutil.GetSecretType(strings.TrimSpace(secretObj.Type)) != v1.SecretTypeOpaque { + return fmt.Errorf("secret provider class %s/%s cannot use secretObjects[*].syncAll for non-opaque secrets", spc.Namespace, spc.Name) + } + + spcutil.BuildSecretObjectData(files, secretObj) + } + } + for _, secretObj := range spc.Spec.SecretObjects { secretName := strings.TrimSpace(secretObj.SecretName) diff --git a/pkg/util/secretutil/secret.go b/pkg/util/secretutil/secret.go index 29d51c7d3..8b2d78433 100644 --- a/pkg/util/secretutil/secret.go +++ b/pkg/util/secretutil/secret.go @@ -38,8 +38,15 @@ const ( privateKeyType = "PRIVATE KEY" privateKeyTypeRSA = "RSA PRIVATE KEY" privateKeyTypeEC = "EC PRIVATE KEY" + basicAuthUsername = "username" + basicAuthPassword = "password" ) +type basicAuthCreds struct { + Username string + Password string +} + // getCertPart returns the certificate or the private key part of the cert func GetCertPart(data []byte, key string) ([]byte, error) { if key == corev1.TLSPrivateKeyKey { @@ -121,6 +128,16 @@ func getPrivateKey(data []byte) ([]byte, error) { return pem.EncodeToMemory(block), nil } +// getCredentials parses the mounted content and returns the required +// key-value pairs for a kubernetes.io/basic-auth K8s secret +func getCredentials(data []byte) basicAuthCreds { + credentials := strings.Split(string(data), ",") + return basicAuthCreds{ + Username: credentials[0], + Password: credentials[1], + } +} + // GetSecretType returns a k8s secret type. // Kubernetes doesn't impose any constraints on the type name: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types // If the secret type is empty, then default is Opaque. @@ -155,7 +172,7 @@ func GetSecretData(secretObjData []*secretsstorev1.SecretObjectData, secretType dataKey := strings.TrimSpace(data.Key) if len(objectName) == 0 { - return datamap, fmt.Errorf("object name in secretObjects.data") + return datamap, fmt.Errorf("object name in secretObjects.data is empty") } if len(dataKey) == 0 { return datamap, fmt.Errorf("key in secretObjects.data is empty") @@ -176,6 +193,13 @@ func GetSecretData(secretObjData []*secretsstorev1.SecretObjectData, secretType } datamap[dataKey] = c } + if secretType == corev1.SecretTypeBasicAuth { + credentials := getCredentials(content) + delete(datamap, dataKey) + + datamap[basicAuthUsername] = []byte(credentials.Username) + datamap[basicAuthPassword] = []byte(credentials.Password) + } } return datamap, nil } diff --git a/pkg/util/spcutil/secret_object_data.go b/pkg/util/spcutil/secret_object_data.go new file mode 100644 index 000000000..7d875941a --- /dev/null +++ b/pkg/util/spcutil/secret_object_data.go @@ -0,0 +1,186 @@ +/* +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 spcutil + +import ( + "strings" + + corev1 "k8s.io/api/core/v1" + secretsstorev1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1" +) + +const ( + tlsKey = "tls.key" + tlsCert = "tls.crt" + dockerConfigJsonKey = ".dockerconfigjson" + sshPrivateKey = "ssh-privatekey" +) + +// BuildSecretObjectData builds the .Spec.SecretObjects[*].Data list of a SecretObject when SyncAll is true +func BuildSecretObjectData(files map[string]string, secretObj *secretsstorev1.SecretObject) { + + for key := range files { + var ( + nested []string + renamedKey string + ) + + nested = strings.Split(key, "/") + if len(nested) > 0 { + renamedKey = strings.Join(nested, "-") + } + + if renamedKey == "" { + secretObj.Data = append(secretObj.Data, &secretsstorev1.SecretObjectData{ + ObjectName: key, + Key: key, + }) + continue + } + + secretObj.Data = append(secretObj.Data, &secretsstorev1.SecretObjectData{ + ObjectName: key, + Key: renamedKey, + }) + } +} + +// BuildSecretObjects builds the .Spec.SecretObjects list of a SecretProviderClass when .SyncOptions.SyncAll is true +// How a SecretObject is built is dependent on the type of secret +func BuildSecretObjects(files map[string]string, secretType corev1.SecretType) []*secretsstorev1.SecretObject { + var ( + secretObject *secretsstorev1.SecretObject + secretObjects []*secretsstorev1.SecretObject + ) + + secretObjects = make([]*secretsstorev1.SecretObject, 0) + for key := range files { + + switch secretType { + case corev1.SecretTypeOpaque: + secretObject = createOpaqueSecretDataObject(key) + case corev1.SecretTypeTLS: + secretObject = createTLSSecretDataObject(key) + case corev1.SecretTypeDockerConfigJson: + secretObject = createDockerConfigJsonSecretDataObject(key) + case corev1.SecretTypeBasicAuth: + secretObject = createBasicAuthSecretDataObject(key) + case corev1.SecretTypeSSHAuth: + secretObject = createSSHSecretDataObject(key) + } + + secretObjects = append(secretObjects, secretObject) + } + + return secretObjects +} + +// createOpaqueSecretDataObject creates a SecretObject for an Opaque secret +func createOpaqueSecretDataObject(key string) *secretsstorev1.SecretObject { + return &secretsstorev1.SecretObject{ + SecretName: setSecretName(key), + Type: string(corev1.SecretTypeOpaque), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: key, + Key: setKey(key), + }, + }, + } +} + +// createTLSSecretDataObject creates a SecretObject for an TLS secret +func createTLSSecretDataObject(key string) *secretsstorev1.SecretObject { + return &secretsstorev1.SecretObject{ + SecretName: setSecretName(key), + Type: string(corev1.SecretTypeTLS), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: key, + Key: tlsKey, + }, + { + ObjectName: key, + Key: tlsCert, + }, + }, + } +} + +// createDockerConfigJsonSecretDataObject creates a SecretObject for an DockerConfigJSON secret +func createDockerConfigJsonSecretDataObject(key string) *secretsstorev1.SecretObject { + return &secretsstorev1.SecretObject{ + SecretName: setSecretName(key), + Type: string(corev1.SecretTypeDockerConfigJson), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: key, + Key: dockerConfigJsonKey, + }, + }, + } +} + +// createBasicAuthSecretDataObject creates a SecretObject for an Basic-Auth secret +func createBasicAuthSecretDataObject(key string) *secretsstorev1.SecretObject { + return &secretsstorev1.SecretObject{ + SecretName: setSecretName(key), + Type: string(corev1.SecretTypeBasicAuth), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: key, + Key: setKey(key), + }, + }, + } +} + +// createSSHSecretDataObject creates a SecretObject for an SSH-Auth secret +func createSSHSecretDataObject(key string) *secretsstorev1.SecretObject { + return &secretsstorev1.SecretObject{ + SecretName: setSecretName(key), + Type: string(corev1.SecretTypeSSHAuth), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: key, + Key: sshPrivateKey, + }, + }, + } +} + +// setSecretName sets the name of a secret to the value of "objectName" separated by "-" +func setSecretName(key string) string { + nested := strings.Split(key, "/") + + if len(nested) > 0 { + return strings.Join(nested, "-") + } + + return key +} + +// setKey sets the key of a secret to the name of the mounted file +func setKey(key string) string { + nested := strings.Split(key, "/") + + if len(nested) > 0 { + return nested[len(nested)-1] + } + + return key +} diff --git a/pkg/util/spcutil/secret_object_data_test.go b/pkg/util/spcutil/secret_object_data_test.go new file mode 100644 index 000000000..a5d0499e6 --- /dev/null +++ b/pkg/util/spcutil/secret_object_data_test.go @@ -0,0 +1,374 @@ +/* +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 spcutil + +import ( + "reflect" + "sort" + "testing" + + corev1 "k8s.io/api/core/v1" + secretsstorev1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1" +) + +const ( + cert = ` +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgIJAP0J5Z7N0Y5fMA0GCSqGSIb3DQEBCwUAMDMxFzAVBgNV +BAMMDmRlbW8uYXp1cmUuY29tMRgwFgYDVQQKDA9ha3MtaW5ncmVzcy10bHMwHhcN +MjAwNDE1MDQyMzQ2WhcNMjEwNDE1MDQyMzQ2WjAzMRcwFQYDVQQDDA5kZW1vLmF6 +dXJlLmNvbTEYMBYGA1UECgwPYWtzLWluZ3Jlc3MtdGxzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAyS3Zky3n8JlLBxPLzgUpKZYxvzRadeWLmWVbK9by +o08S0Ss8Jao7Ay1wHtnLbn52rzCX6IX1sAe1TAT755Gk7JtLMkshtj6F8BNeelEy +E1gsBE5ntY5vyLTm/jZUIKz2Z9TLnqvQTmp6gJ68BKJ1NobnsHiAcKc6hI7kmY9C +oshmAi5qiKYBgzv/thji0093vtVSa9iwHhQp+AEIMhkvM5ZZkiU5eE6MT9SBEcVW +KmWF28UsB04daYwS2MKJ5l6d4n0LUdAG0FBt1lCoT9rwUDj9l3Mqmi953gw26LUr +NrYnM/8N2jl7Cuyw5alIWaUDrt5i+pu8wdWfzVk+fO7x8QIDAQABo1AwTjAdBgNV +HQ4EFgQUwFBbR014McETdrGGklpEQcl71Q0wHwYDVR0jBBgwFoAUwFBbR014McET +drGGklpEQcl71Q0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATgTy +gg1Q6ISSekiBCe12dqUTMFQh9GKpfYWKRbMtjOjpc7Mdwkdmm3Fu6l3RfEFT28Ij +fy97LMYv8W7beemDFqdmneb2w2ww0ZAFJg+GqIJZ9s/JadiFBDNU7CmJMhA225Qz +XC8ovejiePslnL4QJWlhVG93ZlBJ6SDkRgfcoIW2x4IBE6wv7jmRF4lOvb3z1ddP +iPQqhbEEbwMpXmWv7/2RnjAHdjdGaWRMC5+CaI+lqHyj6ir1c+e6u1QUY54qjmgM +koN/frqYab5Ek3kauj1iqW7rPkrFCqT2evh0YRqb1bFsCLJrRNxnOZ5wKXV/OYQa +QX5t0wFGCZ0KlbXDiw== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJLdmTLefwmUsH +E8vOBSkpljG/NFp15YuZZVsr1vKjTxLRKzwlqjsDLXAe2ctufnavMJfohfWwB7VM +BPvnkaTsm0sySyG2PoXwE156UTITWCwETme1jm/ItOb+NlQgrPZn1Mueq9BOanqA +nrwEonU2hueweIBwpzqEjuSZj0KiyGYCLmqIpgGDO/+2GOLTT3e+1VJr2LAeFCn4 +AQgyGS8zllmSJTl4ToxP1IERxVYqZYXbxSwHTh1pjBLYwonmXp3ifQtR0AbQUG3W +UKhP2vBQOP2XcyqaL3neDDbotSs2ticz/w3aOXsK7LDlqUhZpQOu3mL6m7zB1Z/N +WT587vHxAgMBAAECggEAJb0qIYftCJ9ZCbzW8JDbRefc8SdbCN7Er0PqNHEgFy6Q +MxjPMambZF8ztzXYCaRDk12kQYRPsHPhuJ7+ulQCAjinhIm/izZzXbPkd0GgCSzz +JOOoZNCRe68j3fBHG9IWbyfmAp/sdalXzaT5VE09e7sW323bekaEnbVIgN30/CAS +gI77YdaIhG+PT/pSCOc11MTkBJp+VhT1tEtlRAR78b1RXbGi1oUHRee7C3Ia8IKQ +3L5dPxR9RsYsR2O66908kEi8ZcuIjcbIuRPDXYHY+5Nwm3mXuZlkyjyfxJXsIA8i +qBrQrSpHGgAn1TVlLDSCKPLbkRzBRRvAW0zL/cDTuQKBgQDq/9Yxx9QivAuUxxdE +u0VO5CzzZYFWhDxAXS3/wYyo1YnoPtUz/lGCvMWp0k2aaa0+KTXv2fRCUGSujHW7 +Jfo4kuMPkauAhoXx9QJAcjoK0nNbYEaqoJyMoRID+Qb9XHkj+lmBTmMVgALCT9DI +HekHj/M3b7CknbfWv1sOZ/vpQwKBgQDbKEuP/DWQa9DC5nn5phHD/LWZLG/cMR4X +TmwM/cbfRxM/6W0+/KLAodz4amGRzVlW6ax4k26BSE8Zt/SiyA1DQRTeFloduoqW +iWF4dMeItxw2am+xLREwtoN3FgsJHu2z/O/0aaBAOMLUXIPIyiE4L6OnEPifE/pb +AM8EbM5auwKBgGhdABIRjbtzSa1kEYhbprcXjIL3lE4I4f0vpIsNuNsOInW62dKC +Yk6uaRY3KHGn9uFBSgvf/qMost310R8xCYPwb9htN/4XQAspZTubvv0pY0O0aQ3D +0GJ/8dFD2f/Q/pekyfUsC8Lzm8YRzkXhSqkqG7iF6Kviw08iolyuf2ijAoGBANaA +pRzDvWWisUziKsa3zbGnGdNXVBEPniUvo8A/b7RAK84lWcEJov6qLs6RyPfdJrFT +u3S00LcHICzLCU1+QsTt4U/STtfEKjtXMailnFrq5lk4aiPfOXEVYq1fTOPbesrt +Katu6uOQ6tjRyEbx1/vXXPV7Peztr9/8daMeIAdbAoGBAOYRJ1CzMYQKjWF32Uas +7hhQxyH1QI4nV56Dryq7l/UWun2pfwNLZFqOHD3qm05aznzNKvk9aHAsOPFfUUXO +7sp0Ge5FLMSw1uMNnutcVcMz37KAY2fOoE2xoLM4DU/H2NqDjeGCsOsU1ReRS1vB +J+42JGwBdLV99ruYKVKOWPh4 +-----END PRIVATE KEY----- +` + + sshKey = ` +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBa+0SeK0 +uw8TTDVmQuIbT+AAAAEAAAAAEAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQCrAMIhG+1R +tG5kgrmieNDE+hjblN64keV0EnsaSpCKCBFFnWQGCOaNZaoIJVX088IFCzTkz0QeLHl7qO +cSeBDxdm/GTvphEyh5JTroV+ipDq6+6tJ2YNLksP+NmMW5/A3/byFK7cSglwupJgBdMX7l +oAFZLMCOBiLC4CTHmA74xRHkwHgdNZUiiUzw0eC1fUOoQWKT6W+rsCV/Tk6hh0X7x+qLO8 +GnT8S2GzapMY0HoJTK5yV4m/0KV/Ky90OfTDZnAAHEYjQYvQ2YY0UEcU6KZ71msZZ2XCPY +p1mVpMXJ9b2KCOOMKm8ursla79plYmH6cUaAsonT182rD0lHIZPHFxcRZpZGs5Zp/iyZ41 +MK4vlKwEMRdn9KjByTaS21fOHDO3De9LUwg4Q0DmsUrp8M0lG8BX4fJl51v5TCYh2+X2ds +RIsJenJ24Dmuv5tQrWmr6JMxu3xccUHN/NBMYGESEXjMWKAtSfssARbVI8QRJn21N/838C +JV57cv5l7dI7EAAAWQB3gJeXmZjEBVrHOM77c4bXqlhm55jWwMj79N/jc6lGGmESArjbfY +g85wYR0LEsiXETTnkwpCmCRAuUKj5+vAGyDeWM76KGp4igl8RExF8bv74RFUyN3A7O2EbM +HQ2xYoL4W1KX67AT5KJD3g+4nkV74DWhdlazA2DspL7jSz6yDfDXeYxsxIozvkEhoac3T7 +u1UbfvDZw7MnW95gc3ybm74DH1nso2TQzvHEN9Vq/hXa50e7lJtXJ1nVfIBxda0VNqCAdo +CnTG5Q5UKc2l4dfhqScCwbBWjO2M2YPYMBOUSayigQ6S1sJCYL3TpGUl290Q1cWFWQ+ORl +EfeGe9TKxnY64smQH2LftuO5WDmwvf1Ske5BATtZX6zOUpwjgAQ1Bpj0wpht54O8Rbo8ub +kmh+uaE6k0iUEN2van3Mkq9ok/62J+QeFbK7V+Ag2+l4MI7f5cbfcwyMRyLaLIPvTfQWPQ +3RtQdbQHdcGvfmu6aPkKhN5/uoyBbmWOvIsnlYiZp4oY15CFtIYIyhyaHTm1/2rZnuyC6q +Q6dlWxkZEF6p7Uh7dB+9x/GR+MfCLiFNuYms//r1ZEtYp2ctIgL4W2z09a2xN9jJ4Ij3Kn +amJ/NUen5EiTEdNwZlAtpwfh6/x7eQnbq3bMmJcFdrt27lHQg6k2NOKsDxEoAEn5q3fySH +XRT848yHyDXhCht0AooeFk/ToyzjSWlRTPjgJZ+ryckwqOB/D4UvQH5r5SWhQtTfzGUoO7 +9Ozmj4CiCMtlrdcDiQTbgyR1BPReyoR5Xq+m+S2i0kFzGedTalFxhrhHCoSiuy4YcKCcVd ++xQuuJ7HrEkY78EMoII/Al8TKTyD6FWQGWoqWEVcwBXV4J3KUkk1N3oEaqQwj/TXpGlzUf +17QvEjDepHlCbI/5KBUCl8gzc3ByisRWx6a67VbOOih61304VUHapcAJmduiOBnnSFj2kb +3BwANN5uk+lzbeyWF83VthZXr2WcWDqE/g1Ox87TkBHE8JePlxc3/bPkeD8feC11YmNJzX +91kU2XPOxplD11fCCpIh2tbjRUR1VVpoVuf75FwsUo3ecqSSJ8MbpQduGKCllpBuvo2yLQ +A9Bn836/bEvzbpQhTPrPCC7/Nk9QL+PtgfROcW2uOHiMrNZrAyghboV4KPPtmB9RWCxqoY +viWQMn4wIShrg1qluZBpKrNUr7NedjU7MjY6FMK9nernY4YllQB77/MkJNm74Gu8meRGSn +cGQA3P/xcQXR3QQ6spXTfARUSoDF0eNALhoEKwhFcPGNc8nI1g2Ach/Px567aKuOuThOPT +2Ue5/pe4YzMHKOiStLMgbYD31k9KKjvqNbLV1DJFHb7fPymY3GhfrtxemyX1jbZFxUfiKr +WrUuOa/Vzi1fkrTdC62TGHKlWmQD8dUuuuioTbWyAAmPODcsjurXxQVzTJqO8bwrQGKjEc +E/UPV31EMHWlYOQ35lKYMkxyWXeHA9UhGaC3ns+TMzeRRgbX7LRjhwPo4y1gUo4wEjrqyd +PpLSGRRtq8JVFonxnf5xttHJegXKhVb2VkEquZ2jL3GcQyCh6oaKo0yNIdOuQhI1aA3oxZ +f+Eag3u3IzfZYCBnXsRfZ3j9+L+VmTz4WJx6iRHSQRPrSBzVi3U0I5N+ohxzyhORWr2tpp +jcYZ59Pzboy7FXp5s4JK0TOP+XCK9h6v5T7V5fW6XS5fABiVl94zVc1THnJnmHhBIc+7Dm +hlUYwT+6TWxDOJo8sqp6+NLLTx2UxOqKUWWCUCSvap2ptOvIZRm2Cq5kW3W2FFo7tSftWt +d9H7wnDSuC4Zv1ze8Wo2TbXv222TIysd8rK4j+0dTDibvZ1WVbFs00cEBXKIZxskrSlAIb +C9I7vbQHcdYGWqof7MBPsMcaJ+0= +-----END OPENSSH PRIVATE KEY----- + +` + + dockerconfigjson = "{\"auths\":{\"https://index.docker.io/v1/\":{\"username\":\"user1\",\"password\":\"password1\",\"email\":\"account1@email.com\",\"auth\":\"dXNlcjE6cGFzc3dvcmQx\"}}}" +) + +func TestBuildSecretObjectData(t *testing.T) { + files := map[string]string{ + "username": "test user", + "password": "test password", + "nested/username": "test nested user", + "nested/double/username": "double nested user", + } + + secretObj := &secretsstorev1.SecretObject{ + SecretName: "test-secret", + Type: "Opaque", + SyncAll: true, + } + + BuildSecretObjectData(files, secretObj) + + expected := &secretsstorev1.SecretObject{ + SecretName: "test-secret", + Type: "Opaque", + SyncAll: true, + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "username", + Key: "username", + }, + { + ObjectName: "password", + Key: "password", + }, + { + ObjectName: "nested/username", + Key: "nested-username", + }, + { + ObjectName: "nested/double/username", + Key: "nested-double-username", + }, + }, + } + + if ok := assertSecretsObjectsEqual(expected, secretObj); !ok { + t.Fatal("secret objects did not match") + } +} + +func TestBuildSecretObjects(t *testing.T) { + tests := []struct { + files map[string]string + secretType corev1.SecretType + expected []*secretsstorev1.SecretObject + }{ + { + files: map[string]string{ + "username": "test user", + "password": "test password", + "nested/username": "test nested user", + "nested/double/username": "double nested user", + }, + secretType: corev1.SecretTypeOpaque, + expected: []*secretsstorev1.SecretObject{ + { + SecretName: "username", + Type: string(corev1.SecretTypeOpaque), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "username", + Key: "username", + }, + }, + }, + { + SecretName: "password", + Type: string(corev1.SecretTypeOpaque), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "password", + Key: "password", + }, + }, + }, + { + SecretName: "nested-username", + Type: string(corev1.SecretTypeOpaque), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "nested/username", + Key: "username", + }, + }, + }, + { + SecretName: "nested-double-username", + Type: string(corev1.SecretTypeOpaque), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "nested/double/username", + Key: "username", + }, + }, + }, + }, + }, { + files: map[string]string{ + "cert": cert, + }, + secretType: corev1.SecretTypeTLS, + expected: []*secretsstorev1.SecretObject{ + { + SecretName: "cert", + Type: string(corev1.SecretTypeTLS), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "cert", + Key: "tls.key", + }, + { + ObjectName: "cert", + Key: "tls.crt", + }, + }, + }, + }, + }, { + files: map[string]string{ + "basic/basic1": "my-username1,my-password1", + "basic/basic2": "my-username2,my-password2", + "basic/basic3": "my-username3,my-password3", + }, + secretType: corev1.SecretTypeBasicAuth, + expected: []*secretsstorev1.SecretObject{ + { + SecretName: "basic-basic1", + Type: string(corev1.SecretTypeBasicAuth), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "basic/basic1", + Key: "basic1", + }, + }, + }, + { + SecretName: "basic-basic2", + Type: string(corev1.SecretTypeBasicAuth), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "basic/basic2", + Key: "basic2", + }, + }, + }, + { + SecretName: "basic-basic3", + Type: string(corev1.SecretTypeBasicAuth), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "basic/basic3", + Key: "basic3", + }, + }, + }, + }, + }, { + files: map[string]string{ + "ssh": sshKey, + }, + secretType: corev1.SecretTypeSSHAuth, + expected: []*secretsstorev1.SecretObject{ + { + SecretName: "ssh", + Type: string(corev1.SecretTypeSSHAuth), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "ssh", + Key: "ssh-privatekey", + }, + }, + }, + }, + }, + { + files: map[string]string{ + "dev/docker": dockerconfigjson, + }, + secretType: corev1.SecretTypeDockerConfigJson, + expected: []*secretsstorev1.SecretObject{ + { + SecretName: "dev-docker", + Type: string(corev1.SecretTypeDockerConfigJson), + Data: []*secretsstorev1.SecretObjectData{ + { + ObjectName: "dev/docker", + Key: ".dockerconfigjson", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + actualSecretObjects := BuildSecretObjects(test.files, test.secretType) + if ok := assertSecretObjectSlicesEqual(test.expected, actualSecretObjects); !ok { + t.Fatal("secret object slices did not match") + } + } + +} + +func assertSecretsObjectsEqual(expected, actual *secretsstorev1.SecretObject) bool { + if expected.SecretName != actual.SecretName { + return false + } + + if expected.Type != actual.Type { + return false + } + + sort.Slice(expected.Data, func(i, j int) bool { + return expected.Data[i].ObjectName < expected.Data[j].ObjectName + }) + + sort.Slice(actual.Data, func(i, j int) bool { + return actual.Data[i].ObjectName < actual.Data[j].ObjectName + }) + + return reflect.DeepEqual(expected.Data, actual.Data) +} + +func assertSecretObjectSlicesEqual(expected, actual []*secretsstorev1.SecretObject) bool { + if len(expected) != len(actual) { + return false + } + + sort.Slice(expected, func(i, j int) bool { + return expected[i].SecretName < expected[j].SecretName + }) + + sort.Slice(actual, func(i, j int) bool { + return actual[i].SecretName < actual[j].SecretName + }) + + for i := 0; i < len(expected); i++ { + if ok := assertSecretsObjectsEqual(expected[i], actual[i]); !ok { + return false + } + } + + return true +} diff --git a/test/bats/e2e-provider.bats b/test/bats/e2e-provider.bats index ca3834a73..35654ecd4 100644 --- a/test/bats/e2e-provider.bats +++ b/test/bats/e2e-provider.bats @@ -112,6 +112,15 @@ export API_VERSION=$(get_secrets_store_api_version) wait_for_process $WAIT_TIME $SLEEP_TIME "$cmd" } +@test "deploy e2e-provider-sync-all v1 secretproviderclass crd" { + envsubst < $BATS_TESTS_DIR/e2e_provider_sync_allk8s_v1_secretproviderclass.yaml | kubectl apply -f - + + kubectl wait --for condition=established --timeout=60s crd/secretproviderclasses.secrets-store.csi.x-k8s.io + + cmd="kubectl get secretproviderclasses.secrets-store.csi.x-k8s.io/e2e-provider-sync-all -o yaml | grep e2e-provider-sync-all" + wait_for_process $WAIT_TIME $SLEEP_TIME "$cmd" +} + @test "CSI inline volume test with pod portability" { envsubst < $BATS_TESTS_DIR/pod-secrets-store-inline-volume-crd.yaml | kubectl apply -f - @@ -170,21 +179,44 @@ export API_VERSION=$(get_secrets_store_api_version) kubectl wait --for=condition=Ready --timeout=90s pod -l app=busybox } +@test "Sync all with K8s secrets - create deployment" { + envsubst < $BATS_TESTS_DIR/e2e_provider_sync_allk8s_v1_secretproviderclass.yaml | kubectl apply -f - + + kubectl wait --for condition=established --timeout=60s crd/secretproviderclasses.secrets-store.csi.x-k8s.io + + cmd="kubectl get secretproviderclasses.secrets-store.csi.x-k8s.io/e2e-provider-sync-all -o yaml | grep e2e-provider-sync-all" + wait_for_process $WAIT_TIME $SLEEP_TIME "$cmd" + + envsubst < $BATS_TESTS_DIR/deployment-sync_allk8s-e2e-provider-sync-all.yaml | kubectl apply -f - + + kubectl wait --for=condition=Ready --timeout=90s pod -l app=busybox-sync-all +} + @test "Sync with K8s secrets - read secret from pod, read K8s secret, read env var, check secret ownerReferences with multiple owners" { POD=$(kubectl get pod -l app=busybox -o jsonpath="{.items[0].metadata.name}") + SYNC_ALL_POD=$(kubectl get pod -l app=busybox-sync-all -o jsonpath="{.items[0].metadata.name}") result=$(kubectl exec $POD -- cat /mnt/secrets-store/$SECRET_NAME) [[ "${result//$'\r'}" == "${SECRET_VALUE}" ]] + result=$(kubectl exec $SYNC_ALL_POD -- cat /mnt/secrets-store/$SECRET_NAME) + [[ "${result//$'\r'}" == "${SECRET_VALUE}" ]] + result=$(kubectl exec $POD -- cat /mnt/secrets-store/$KEY_NAME) result_base64_encoded=$(echo "${result//$'\r'}" | base64 ${BASE64_FLAGS}) [[ "${result_base64_encoded}" == *"${KEY_VALUE_CONTAINS}"* ]] + result=$(kubectl exec $SYNC_ALL_POD -- cat /mnt/secrets-store/$KEY_NAME) + result_base64_encoded=$(echo "${result//$'\r'}" | base64 ${BASE64_FLAGS}) + [[ "${result_base64_encoded}" == *"${KEY_VALUE_CONTAINS}"* ]] + result=$(kubectl get secret foosecret -o jsonpath="{.data.username}" | base64 -d) [[ "${result//$'\r'}" == "${SECRET_VALUE}" ]] result=$(kubectl exec $POD -- printenv | grep SECRET_USERNAME) | awk -F"=" '{ print $2}' [[ "${result//$'\r'}" == "${SECRET_VALUE}" ]] + result=$(kubectl exec $SYNC_ALL_POD -- printenv | grep SECRET_USERNAME) | awk -F"=" '{ print $2}' + [[ "${result//$'\r'}" == "${SECRET_VALUE}" ]] result=$(kubectl get secret foosecret -o jsonpath="{.metadata.labels.environment}") [[ "${result//$'\r'}" == "${LABEL_VALUE}" ]] @@ -192,7 +224,7 @@ export API_VERSION=$(get_secrets_store_api_version) result=$(kubectl get secret foosecret -o jsonpath="{.metadata.labels.secrets-store\.csi\.k8s\.io/managed}") [[ "${result//$'\r'}" == "true" ]] - run wait_for_process $WAIT_TIME $SLEEP_TIME "compare_owner_count foosecret default 2" + run wait_for_process $WAIT_TIME $SLEEP_TIME "compare_owner_count foosecret default 3" assert_success } @@ -204,16 +236,23 @@ export API_VERSION=$(get_secrets_store_api_version) run kubectl delete -f $BATS_TESTS_DIR/deployment-synck8s-e2e-provider.yaml assert_success - run wait_for_process $WAIT_TIME $SLEEP_TIME "compare_owner_count foosecret default 1" + run wait_for_process $WAIT_TIME $SLEEP_TIME "compare_owner_count foosecret default 2" assert_success run kubectl delete -f $BATS_TESTS_DIR/deployment-two-synck8s-e2e-provider.yaml assert_success + run wait_for_process $WAIT_TIME $SLEEP_TIME "compare_owner_count foosecret default 1" + assert_success + + run kubectl delete -f $BATS_TESTS_DIR/deployment-sync_allk8s-e2e-provider-sync-all.yaml + assert_success + run wait_for_process $WAIT_TIME $SLEEP_TIME "check_secret_deleted foosecret default" assert_success envsubst < $BATS_TESTS_DIR/e2e_provider_synck8s_v1_secretproviderclass.yaml | kubectl delete -f - + envsubst < $BATS_TESTS_DIR/e2e_provider_sync_allk8s_v1_secretproviderclass.yaml | kubectl delete -f - } @test "Test Namespaced scope SecretProviderClass - create deployment" { diff --git a/test/bats/tests/e2e_provider/deployment-sync_allk8s-e2e-provider-sync-all.yaml b/test/bats/tests/e2e_provider/deployment-sync_allk8s-e2e-provider-sync-all.yaml new file mode 100644 index 000000000..1b134dd4c --- /dev/null +++ b/test/bats/tests/e2e_provider/deployment-sync_allk8s-e2e-provider-sync-all.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busybox-deployment-sync-all + labels: + app: busybox-sync-all +spec: + replicas: 1 + selector: + matchLabels: + app: busybox-sync-all + template: + metadata: + labels: + app: busybox-sync-all + spec: + terminationGracePeriodSeconds: 0 + containers: + - image: k8s.gcr.io/e2e-test-images/busybox:1.29 + name: busybox + imagePullPolicy: IfNotPresent + command: + - "/bin/sleep" + - "10000" + env: + - name: SECRET_USERNAME + valueFrom: + secretKeyRef: + name: foosecret + key: username + volumeMounts: + - name: secrets-store-inline + mountPath: "/mnt/secrets-store" + readOnly: true + volumes: + - name: secrets-store-inline + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "e2e-provider-sync-all" diff --git a/test/bats/tests/e2e_provider/e2e_provider_sync_allk8s_v1_secretproviderclass.yaml b/test/bats/tests/e2e_provider/e2e_provider_sync_allk8s_v1_secretproviderclass.yaml new file mode 100644 index 000000000..648c809dd --- /dev/null +++ b/test/bats/tests/e2e_provider/e2e_provider_sync_allk8s_v1_secretproviderclass.yaml @@ -0,0 +1,26 @@ +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: e2e-provider-sync-all +spec: + provider: e2e-provider + secretObjects: + - secretName: foosecret + type: Opaque + labels: + environment: "test" + data: + - objectName: $SECRET_NAME + key: username + parameters: + objects: | + array: + - | + objectName: $SECRET_NAME + objectVersion: $SECRET_VERSION + - | + objectName: $KEY_NAME + objectVersion: $KEY_VERSION + syncOptions: + type: Opaque + syncAll: true diff --git a/test/bats/tests/e2e_provider/e2e_provider_v1_multiple_secretproviderclass.yaml b/test/bats/tests/e2e_provider/e2e_provider_v1_multiple_secretproviderclass.yaml index eb8b171ca..0d720ffb5 100644 --- a/test/bats/tests/e2e_provider/e2e_provider_v1_multiple_secretproviderclass.yaml +++ b/test/bats/tests/e2e_provider/e2e_provider_v1_multiple_secretproviderclass.yaml @@ -5,11 +5,11 @@ metadata: spec: provider: e2e-provider secretObjects: - - secretName: foosecret-0 - type: Opaque - data: - - objectName: $SECRET_NAME - key: username + - secretName: foosecret-0 + type: Opaque + data: + - objectName: $SECRET_NAME + key: username parameters: objects: | array: @@ -27,11 +27,11 @@ metadata: spec: provider: e2e-provider secretObjects: - - secretName: foosecret-1 - type: Opaque - data: - - objectName: $SECRET_NAME - key: username + - secretName: foosecret-1 + type: Opaque + data: + - objectName: $SECRET_NAME + key: username parameters: objects: | array: