diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 7a350938f..de7411cac 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1725,7 +1725,8 @@ export const SecretSyncs = { SYNC_OPTIONS: (destination: SecretSync) => { const destinationName = SECRET_SYNC_NAME_MAP[destination]; return { - initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.` + initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`, + disableSecretDeletion: `Enable this flag to prevent removal of secrets when syncing from the ${destinationName} destination.` }; }, ADDITIONAL_SYNC_OPTIONS: { diff --git a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts index 9ca035774..7e77bd256 100644 --- a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts +++ b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts @@ -382,6 +382,8 @@ export const AwsParameterStoreSyncFns = { } } + if (syncOptions.disableSecretDeletion) return; + const parametersToDelete: AWS.SSM.Parameter[] = []; for (const entry of Object.entries(awsParameterStoreSecretsRecord)) { diff --git a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts index d4f272475..7cea12d1b 100644 --- a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts +++ b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts @@ -396,6 +396,8 @@ export const AwsSecretsManagerSyncFns = { } } + if (syncOptions.disableSecretDeletion) return; + for await (const secretKey of Object.keys(awsSecretsRecord)) { if (!(secretKey in secretMap) || !secretMap[secretKey].value) { try { diff --git a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts index 8c0587599..64d82c125 100644 --- a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts +++ b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts @@ -136,6 +136,8 @@ export const azureAppConfigurationSyncFactory = ({ } } + if (secretSync.syncOptions.disableSecretDeletion) return; + for await (const key of Object.keys(azureAppConfigSecrets)) { const azureSecret = azureAppConfigSecrets[key]; if ( diff --git a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts index 21fef297a..b50d78bb2 100644 --- a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts +++ b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts @@ -189,6 +189,8 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur }); } + if (secretSync.syncOptions.disableSecretDeletion) return; + for await (const deleteSecretKey of deleteSecrets.filter( (secret) => !setSecrets.find((setSecret) => setSecret.key === secret) )) { diff --git a/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts b/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts index 2d450b2b1..2ee7977a4 100644 --- a/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts +++ b/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts @@ -112,6 +112,8 @@ export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabri accessToken }); + if (secretSync.syncOptions.disableSecretDeletion) return; + for await (const secret of databricksSecretKeys) { if (!(secret.key in secretMap)) { await deleteDatabricksSecrets({ diff --git a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts index f0eb3ce88..27ad2cba6 100644 --- a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts +++ b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts @@ -147,6 +147,9 @@ export const GcpSyncFns = { for await (const key of Object.keys(gcpSecrets)) { try { if (!(key in secretMap) || !secretMap[key].value) { + // eslint-disable-next-line no-continue + if (secretSync.syncOptions.disableSecretDeletion) continue; + // case: delete secret await request.delete( `${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${destinationConfig.projectId}/secrets/${key}`, diff --git a/backend/src/services/secret-sync/github/github-sync-fns.ts b/backend/src/services/secret-sync/github/github-sync-fns.ts index a09a41163..1fe922de5 100644 --- a/backend/src/services/secret-sync/github/github-sync-fns.ts +++ b/backend/src/services/secret-sync/github/github-sync-fns.ts @@ -192,12 +192,6 @@ export const GithubSyncFns = { const publicKey = await getPublicKey(client, secretSync); - for await (const encryptedSecret of encryptedSecrets) { - if (!(encryptedSecret.name in secretMap)) { - await deleteSecret(client, secretSync, encryptedSecret); - } - } - await sodium.ready.then(async () => { for await (const key of Object.keys(secretMap)) { // convert secret & base64 key to Uint8Array. @@ -224,6 +218,14 @@ export const GithubSyncFns = { } } }); + + if (secretSync.syncOptions.disableSecretDeletion) return; + + for await (const encryptedSecret of encryptedSecrets) { + if (!(encryptedSecret.name in secretMap)) { + await deleteSecret(client, secretSync, encryptedSecret); + } + } }, getSecrets: async (secretSync: TGitHubSyncWithCredentials) => { throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`); diff --git a/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts b/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts index a07d2d0b5..5fa0a3d63 100644 --- a/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts +++ b/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts @@ -196,6 +196,8 @@ export const HumanitecSyncFns = { } } + if (secretSync.syncOptions.disableSecretDeletion) return; + for await (const humanitecSecret of humanitecSecrets) { if (!secretMap[humanitecSecret.key]) { await deleteSecret(secretSync, humanitecSecret); diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts index 8ea3a4f0b..50ff3f307 100644 --- a/backend/src/services/secret-sync/secret-sync-schemas.ts +++ b/backend/src/services/secret-sync/secret-sync-schemas.ts @@ -23,7 +23,8 @@ const BaseSyncOptionsSchema = ({ initialSyncBehavior: (canImportSecrets ? z.nativeEnum(SecretSyncInitialSyncBehavior) : z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination) - ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior) + ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior), + disableSecretDeletion: z.boolean().optional().describe(SecretSyncs.SYNC_OPTIONS(destination).disableSecretDeletion) }); const schema = merge ? baseSchema.merge(merge) : baseSchema; diff --git a/docs/integrations/secret-syncs/aws-parameter-store.mdx b/docs/integrations/secret-syncs/aws-parameter-store.mdx index 165998841..831d0c752 100644 --- a/docs/integrations/secret-syncs/aws-parameter-store.mdx +++ b/docs/integrations/secret-syncs/aws-parameter-store.mdx @@ -43,8 +43,9 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical." - **KMS Key**: The AWS KMS key ID or alias to encrypt parameters with. - **Tags**: Optional resource tags to add to parameters synced by Infisical. - **Sync Secret Metadata as Resource Tags**: If enabled, metadata attached to secrets will be added as resource tags to parameters synced by Infisical. - Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict. + Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your Parameter Store Sync, then click **Next**. ![Configure Details](/images/secret-syncs/aws-parameter-store/aws-parameter-store-details.png) diff --git a/docs/integrations/secret-syncs/aws-secrets-manager.mdx b/docs/integrations/secret-syncs/aws-secrets-manager.mdx index b8df0e8b3..a301ee86b 100644 --- a/docs/integrations/secret-syncs/aws-secrets-manager.mdx +++ b/docs/integrations/secret-syncs/aws-secrets-manager.mdx @@ -46,7 +46,9 @@ description: "Learn how to configure an AWS Secrets Manager Sync for Infisical." - **KMS Key**: The AWS KMS key ID or alias to encrypt secrets with. - **Tags**: Optional tags to add to secrets synced by Infisical. - **Sync Secret Metadata as Tags**: If enabled, metadata attached to secrets will be added as tags to secrets synced by Infisical. + Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your Secrets Manager Sync, then click **Next**. ![Configure Details](/images/secret-syncs/aws-secrets-manager/aws-secrets-manager-details.png) diff --git a/docs/integrations/secret-syncs/azure-app-configuration.mdx b/docs/integrations/secret-syncs/azure-app-configuration.mdx index 3b443c672..35a577872 100644 --- a/docs/integrations/secret-syncs/azure-app-configuration.mdx +++ b/docs/integrations/secret-syncs/azure-app-configuration.mdx @@ -6,7 +6,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic **Prerequisites:** - Set up and add secrets to [Infisical Cloud](https://app.infisical.com) - - Create a [Azure Connection](/integrations/app-connections/azure), configured for Azure App Configuration. + - Create an [Azure App Configuration Connection](/integrations/app-connections/azure-app-configuration) The Azure App Configuration Secret Sync requires the following permissions to be set on the user / service principal @@ -50,6 +50,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic - **Import Secrets (Prioritize Azure App Configuration)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your Azure App Configuration Sync, then click **Next**. ![Configure Details](/images/secret-syncs/azure-app-configuration/app-config-details.png) diff --git a/docs/integrations/secret-syncs/azure-key-vault.mdx b/docs/integrations/secret-syncs/azure-key-vault.mdx index 9b28f841f..5f55a73ae 100644 --- a/docs/integrations/secret-syncs/azure-key-vault.mdx +++ b/docs/integrations/secret-syncs/azure-key-vault.mdx @@ -6,7 +6,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical." **Prerequisites:** - Set up and add secrets to [Infisical Cloud](https://app.infisical.com) - - Create a [Azure Connection](/integrations/app-connections/azure), configured for Azure Key Vault. + - Create an [Azure Key Vault Connection](/integrations/app-connections/azure-key-vault) The Azure Key Vault Secret Sync requires the following secrets permissions to be set on the user / service principal @@ -52,6 +52,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical." - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict. - **Import Secrets (Prioritize Azure Key Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your Azure Key Vault Sync, then click **Next**. ![Configure Details](/images/secret-syncs/azure-key-vault/vault-details.png) diff --git a/docs/integrations/secret-syncs/databricks.mdx b/docs/integrations/secret-syncs/databricks.mdx index 148542708..c9db5f88a 100644 --- a/docs/integrations/secret-syncs/databricks.mdx +++ b/docs/integrations/secret-syncs/databricks.mdx @@ -47,6 +47,7 @@ description: "Learn how to configure a Databricks Sync for Infisical." Databricks does not support importing secrets. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your Databricks Sync, then click **Next**. ![Configure Details](/images/secret-syncs/databricks/databricks-details.png) diff --git a/docs/integrations/secret-syncs/gcp-secret-manager.mdx b/docs/integrations/secret-syncs/gcp-secret-manager.mdx index bcb3b87bd..72c932116 100644 --- a/docs/integrations/secret-syncs/gcp-secret-manager.mdx +++ b/docs/integrations/secret-syncs/gcp-secret-manager.mdx @@ -43,6 +43,7 @@ description: "Learn how to configure a GCP Secret Manager Sync for Infisical." - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over GCP Secret Manager when keys conflict. - **Import Secrets (Prioritize GCP Secret Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from GCP Secret Manager over Infisical when keys conflict. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your GCP Secret Manager Sync, then click **Next**. ![Configure Details](/images/secret-syncs/gcp-secret-manager/gcp-secret-manager-details.png) diff --git a/docs/integrations/secret-syncs/github.mdx b/docs/integrations/secret-syncs/github.mdx index d7d7afd86..d55ec3d0b 100644 --- a/docs/integrations/secret-syncs/github.mdx +++ b/docs/integrations/secret-syncs/github.mdx @@ -63,6 +63,7 @@ description: "Learn how to configure a GitHub Sync for Infisical." GitHub does not support importing secrets. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your GitHub Sync, then click **Next**. ![Configure Details](/images/secret-syncs/github/github-details.png) diff --git a/docs/integrations/secret-syncs/humanitec.mdx b/docs/integrations/secret-syncs/humanitec.mdx index 0edc2098e..e8cd7eafc 100644 --- a/docs/integrations/secret-syncs/humanitec.mdx +++ b/docs/integrations/secret-syncs/humanitec.mdx @@ -56,6 +56,7 @@ description: "Learn how to configure a Humanitec Sync for Infisical." Humanitec does not support importing secrets. - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. 6. Configure the **Details** of your Humanitec Sync, then click **Next**. ![Configure Details](/images/secret-syncs/humanitec/humanitec-details.png) diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index d3213caf7..802c9cc7c 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -1,9 +1,9 @@ import { ReactNode } from "react"; import { Controller, useFormContext } from "react-hook-form"; -import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; +import { faQuestionCircle, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { FormControl, Select, SelectItem } from "@app/components/v2"; +import { FormControl, Select, SelectItem, Switch, Tooltip } from "@app/components/v2"; import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs"; @@ -116,6 +116,44 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { )} {AdditionalSyncOptionsFieldsComponent} + { + return ( + + +

+ Disable Secret Deletion{" "} + +

+ When enabled, Infisical will not{" "} + remove secrets from the sync destination. +

+

+ Enable this option if you intend to manage some secrets manually outside + of Infisical. +

+ + } + > + + +

+
+
+ ); + }} + /> {/* ( { secretPath, syncOptions: { // appendSuffix, prependPrefix, + disableSecretDeletion, initialSyncBehavior }, destination, @@ -111,6 +112,11 @@ export const SecretSyncReviewFields = () => { {/* {prependPrefix} {appendSuffix} */} {AdditionalSyncOptionsFieldsComponent} + {disableSecretDeletion && ( + + Enabled + + )}
diff --git a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts index b898fe91f..bf72321ce 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts @@ -7,7 +7,8 @@ export const BaseSecretSyncSchema = { const baseSyncOptionsSchema = z.object({ - initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior) + initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior), + disableSecretDeletion: z.boolean().optional().default(false) // scott: removed temporarily for evaluation of template formatting // prependPrefix: z // .string() diff --git a/frontend/src/hooks/api/secretSyncs/types/root-sync.ts b/frontend/src/hooks/api/secretSyncs/types/root-sync.ts index 49cf9c978..dfbbcd063 100644 --- a/frontend/src/hooks/api/secretSyncs/types/root-sync.ts +++ b/frontend/src/hooks/api/secretSyncs/types/root-sync.ts @@ -3,6 +3,7 @@ import { SecretSyncInitialSyncBehavior, SecretSyncStatus } from "@app/hooks/api/ export type RootSyncOptions = { initialSyncBehavior: SecretSyncInitialSyncBehavior; + disableSecretDeletion?: boolean; // prependPrefix?: string; // appendSuffix?: string; }; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx index be48428ea..139fcf916 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; import { SecretSyncLabel } from "@app/components/secret-syncs"; -import { IconButton } from "@app/components/v2"; +import { Badge, IconButton } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs"; @@ -24,7 +24,8 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = syncOptions: { // appendSuffix, // prependPrefix, - initialSyncBehavior + initialSyncBehavior, + disableSecretDeletion } } = secretSync; @@ -58,24 +59,22 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =

Sync Options

- {AdditionalSyncOptionsComponent && ( - - {(isAllowed) => ( - - - - )} - - )} + + {(isAllowed) => ( + + + + )} +
@@ -85,6 +84,11 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = {/* {prependPrefix} {appendSuffix} */} {AdditionalSyncOptionsComponent} + {disableSecretDeletion && ( + + Enabled + + )}