diff --git a/backend/src/validation/secrets.ts b/backend/src/validation/secrets.ts index f769fcfe2..7c0f2d69f 100644 --- a/backend/src/validation/secrets.ts +++ b/backend/src/validation/secrets.ts @@ -429,6 +429,9 @@ export const UpdateSecretByNameBatchV3 = z.object({ secretValueCiphertext: z.string().trim(), secretValueIV: z.string().trim(), secretValueTag: z.string().trim(), + secretKeyCiphertext: z.string().trim(), + secretKeyIV: z.string().trim(), + secretKeyTag: z.string().trim(), secretCommentCiphertext: z.string().trim().optional(), secretCommentIV: z.string().trim().optional(), secretCommentTag: z.string().trim().optional(), diff --git a/docs/documentation/platform/secret-rotation/postgres.mdx b/docs/documentation/platform/secret-rotation/postgres.mdx index 167b60fcd..f70e8a70b 100644 --- a/docs/documentation/platform/secret-rotation/postgres.mdx +++ b/docs/documentation/platform/secret-rotation/postgres.mdx @@ -1,6 +1,6 @@ --- title: "PostgreSQL/CockroachDB" -description: "Rotated database user password of a postgreSQL or cochroach db" +description: "Rotated database user password of a postgreSQL or cockroach db" --- Infisical will update periodically the provided database user's password. diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 942a60100..7ddb616b2 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -161,12 +161,65 @@ Default re-sync interval is every 1 minute. -The `managedSecretReference` field in the InfisicalSecret resource is used to specify the location where secrets retrieved from an Infisical project should be stored. -You should specify the name and namespace of the Kubernetes secret that will hold these secrets. The operator will create the secret for you, you just need to provide its name and namespace. +The `managedSecretReference` field is used to define the target location for storing secrets retrieved from an Infisical project. +This field requires specifying both the name and namespace of the Kubernetes secret that will hold these secrets. +The Infisical operator will automatically create the Kubernetes secret with the specified name/namespace and keep it continuously updated. -The managed secret be should be created in the same namespace as the deployment that will use it. +Note: The managed secret be should be created in the same namespace as the deployment that will use it. + +The name of the managed Kubernetes secret to be created + + +The namespace of the managed Kubernetes secret to be created. + + +Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets. + + +### Propagating labels & annotations + +The operator will transfer all labels & annotations present on the `InfisicalSecret` CRD to the managed Kubernetes secret to be created. +Thus, if a specific label is required on the resulting secret, it can be applied as demonstrated in the following example: + + +```yaml +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalSecret +metadata: + name: infisicalsecret-sample + labels: + label-to-be-passed-to-managed-secret: sample-value + annotations: + example.com/annotation-to-be-passed-to-managed-secret: "sample-value" +spec: + .. + authentication: + ... + managedSecretReference: + ... +``` + +This would result in the following managed secret to be created: + +```yaml +apiVersion: v1 +data: + ... +kind: Secret +metadata: + annotations: + example.com/annotation-to-be-passed-to-managed-secret: sample-value + secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw" + labels: + label-to-be-passed-to-managed-secret: sample-value + name: managed-token + namespace: default +type: Opaque +``` + + ### Apply the Infisical CRD to your cluster Once you have configured the Infisical CRD with the required fields, you can apply it to your cluster. diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index c05647627..7c108e66b 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -38,7 +38,7 @@ Other environment variables are listed below to increase the functionality of yo Credential to connect to host (e.g. team@infisical.com) - + Credential to connect to host diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx index 3a33c2688..3fe0d19e3 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/SecretDropzone.tsx @@ -14,6 +14,7 @@ import { Button, Modal, ModalContent } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api"; +import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries"; import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types"; @@ -194,6 +195,7 @@ export const SecretDropzone = ({ queryClient.invalidateQueries( secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) ); + queryClient.invalidateQueries(secretApprovalRequestKeys.count({ workspaceId })); handlePopUpClose("overlapKeyWarning"); createNotification({ type: "success", @@ -222,9 +224,9 @@ export const SecretDropzone = ({ onDragOver={handleDrag} onDrop={handleDrop} className={twMerge( - "relative mx-0.5 mb-4 mt-4 flex cursor-pointer items-center justify-center rounded-md bg-mineshaft-900 py-4 text-sm px-2 text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100", + "relative mx-0.5 mb-4 mt-4 flex cursor-pointer items-center justify-center rounded-md bg-mineshaft-900 py-4 px-2 text-sm text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100", isDragActive && "opacity-100", - !isSmaller && "w-full max-w-3xl flex-col space-y-4 py-20 mx-auto", + !isSmaller && "mx-auto w-full max-w-3xl flex-col space-y-4 py-20", isLoading && "bg-bunker-800" )} > @@ -238,7 +240,7 @@ export const SecretDropzone = ({ /> ) : ( -
+
diff --git a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx index 7db6037fd..2a5232930 100644 --- a/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretListView/SecretListView.tsx @@ -7,6 +7,7 @@ import { CreateTagModal } from "@app/components/tags/CreateTagModal"; import { DeleteActionModal } from "@app/components/v2"; import { usePopUp } from "@app/hooks"; import { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "@app/hooks/api"; +import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries"; import { DecryptedSecret } from "@app/hooks/api/secrets/types"; import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries"; @@ -262,6 +263,7 @@ export const SecretListView = ({ queryClient.invalidateQueries( secretSnapshotKeys.count({ workspaceId, environment, directory: secretPath }) ); + queryClient.invalidateQueries(secretApprovalRequestKeys.count({ workspaceId })); handlePopUpClose("secretDetail"); createNotification({ type: "success", @@ -284,6 +286,7 @@ export const SecretListView = ({ const { key, _id: secretId } = popUp.deleteSecret?.data as DecryptedSecret; try { await handleSecretOperation("delete", "shared", key, { secretId }); + // wrap this in another function and then reuse queryClient.invalidateQueries( secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) ); @@ -293,6 +296,7 @@ export const SecretListView = ({ queryClient.invalidateQueries( secretSnapshotKeys.count({ workspaceId, environment, directory: secretPath }) ); + queryClient.invalidateQueries(secretApprovalRequestKeys.count({ workspaceId })); handlePopUpClose("deleteSecret"); handlePopUpClose("secretDetail"); createNotification({ diff --git a/helm-charts/secrets-operator/Chart.yaml b/helm-charts/secrets-operator/Chart.yaml index 53afe79e4..0fe6c6d8d 100644 --- a/helm-charts/secrets-operator/Chart.yaml +++ b/helm-charts/secrets-operator/Chart.yaml @@ -13,7 +13,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.2.1 +version: 0.3.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml index 51dd18a41..cfee98f81 100644 --- a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml +++ b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml @@ -102,6 +102,11 @@ spec: secretNamespace: description: The name space where the Kubernetes Secret is located type: string + secretType: + default: Opaque + description: 'The Kubernetes Secret type (experimental feature). + More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' + type: string required: - secretName - secretNamespace diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index 3b61bd215..2acad987c 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -43,6 +43,21 @@ type KubeSecretReference struct { SecretNamespace string `json:"secretNamespace"` } +type MangedKubeSecretConfig struct { + // The name of the Kubernetes Secret + // +kubebuilder:validation:Required + SecretName string `json:"secretName"` + + // The name space where the Kubernetes Secret is located + // +kubebuilder:validation:Required + SecretNamespace string `json:"secretNamespace"` + + // The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types + // +kubebuilder:validation:Optional + // +kubebuilder:default:=Opaque + SecretType string `json:"secretType"` +} + // InfisicalSecretSpec defines the desired state of InfisicalSecret type InfisicalSecretSpec struct { // +kubebuilder:validation:Optional @@ -52,7 +67,7 @@ type InfisicalSecretSpec struct { Authentication Authentication `json:"authentication"` // +kubebuilder:validation:Required - ManagedSecretReference KubeSecretReference `json:"managedSecretReference"` + ManagedSecretReference MangedKubeSecretConfig `json:"managedSecretReference"` // +kubebuilder:default:=60 ResyncInterval int `json:"resyncInterval"` diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index 01000431c..5b6befcbc 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -157,6 +157,21 @@ func (in *KubeSecretReference) DeepCopy() *KubeSecretReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MangedKubeSecretConfig) DeepCopyInto(out *MangedKubeSecretConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MangedKubeSecretConfig. +func (in *MangedKubeSecretConfig) DeepCopy() *MangedKubeSecretConfig { + if in == nil { + return nil + } + out := new(MangedKubeSecretConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretScopeInWorkspace) DeepCopyInto(out *SecretScopeInWorkspace) { *out = *in diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 07433f781..c5a6beaa1 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -102,6 +102,11 @@ spec: secretNamespace: description: The name space where the Kubernetes Secret is located type: string + secretType: + default: Opaque + description: 'The Kubernetes Secret type (experimental feature). + More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' + type: string required: - secretName - secretNamespace diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index 4c5059d9f..82addeb4e 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -2,8 +2,12 @@ apiVersion: secrets.infisical.com/v1alpha1 kind: InfisicalSecret metadata: name: infisicalsecret-sample + labels: + label-to-be-passed-to-managed-secret: sample-value + annotations: + example.com/annotation-to-be-passed-to-managed-secret: "sample-value" spec: - hostAPI: http://localhost:8764/api + hostAPI: https://app.infisical.com/api resyncInterval: 10 authentication: serviceAccount: @@ -22,6 +26,8 @@ spec: managedSecretReference: secretName: managed-secret secretNamespace: default + # secretType: kubernetes.io/dockerconfigjson + # # To be depreciated soon # tokenSecretReference: # secretName: service-token diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 509de94ac..2c291a46e 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -72,7 +72,6 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context. // default to new secret ref structure secretName := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretName secretNamespace := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretNamespace - // fall back to previous secret ref if secretName == "" { secretName = infisicalSecret.Spec.TokenSecretReference.SecretName @@ -129,20 +128,34 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV3Response) error { plainProcessedSecrets := make(map[string][]byte) + secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType + for _, secret := range secretsFromAPI { plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process } + // copy labels and annotations from InfisicalSecret CRD + labels := map[string]string{} + for k, v := range infisicalSecret.Labels { + labels[k] = v + } + + annotations := map[string]string{} + for k, v := range infisicalSecret.Annotations { + annotations[k] = v + } + + annotations[SECRET_VERSION_ANNOTATION] = encryptedSecretsResponse.ETag + // create a new secret as specified by the managed secret spec of CRD newKubeSecretInstance := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, - Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, - Annotations: map[string]string{ - SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag, - }, + Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, + Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, + Annotations: annotations, + Labels: labels, }, - Type: "Opaque", + Type: corev1.SecretType(secretType), Data: plainProcessedSecrets, } @@ -151,7 +164,7 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err) } - fmt.Println("Successfully created a managed Kubernetes secret with your Infisical secrets") + fmt.Printf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s\n", secretType) return nil } diff --git a/k8-operator/kubectl-install/install-secrets-operator.yaml b/k8-operator/kubectl-install/install-secrets-operator.yaml index c25b0551f..5a11db30a 100644 --- a/k8-operator/kubectl-install/install-secrets-operator.yaml +++ b/k8-operator/kubectl-install/install-secrets-operator.yaml @@ -108,6 +108,10 @@ spec: secretNamespace: description: The name space where the Kubernetes Secret is located type: string + secretType: + default: Opaque + description: 'The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' + type: string required: - secretName - secretNamespace