diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 04e91051d..c0784a62d 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -1,5 +1,6 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums"; +import { DestinationDuplicateCheckFn } from "@app/services/secret-sync/secret-sync-types"; export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.AWSParameterStore]: "AWS Parameter Store", @@ -99,3 +100,101 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.Netlify]: SecretSyncPlanType.Regular, [SecretSync.Bitbucket]: SecretSyncPlanType.Regular }; + +export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { + [SecretSync.AWSParameterStore]: [], + [SecretSync.AWSSecretsManager]: ["mappingBehavior", "secretName"], + [SecretSync.GitHub]: [], + [SecretSync.GCPSecretManager]: [], + [SecretSync.AzureKeyVault]: [], + [SecretSync.AzureAppConfiguration]: ["label"], + [SecretSync.AzureDevOps]: ["devopsProjectName"], + [SecretSync.Databricks]: [], + [SecretSync.Humanitec]: [], + [SecretSync.TerraformCloud]: ["variableSetName", "workspaceName"], + [SecretSync.Camunda]: [], + [SecretSync.Vercel]: ["appName"], + [SecretSync.Windmill]: [], + [SecretSync.HCVault]: [], + [SecretSync.TeamCity]: [], + [SecretSync.OCIVault]: [], + [SecretSync.OnePass]: ["valueLabel"], + [SecretSync.Heroku]: ["appName"], + [SecretSync.Render]: [], + [SecretSync.Flyio]: [], + [SecretSync.GitLab]: [ + "projectName", + "shouldProtectSecrets", + "shouldMaskSecrets", + "shouldHideSecrets", + "targetEnvironment", + "groupName", + "groupId", + "projectId" + ], + [SecretSync.CloudflarePages]: [], + [SecretSync.CloudflareWorkers]: [], + [SecretSync.Supabase]: ["projectName"], + [SecretSync.Zabbix]: ["hostName", "macroType"], + [SecretSync.Railway]: ["projectName", "environmentName", "serviceName"], + [SecretSync.Checkly]: ["groupName", "accountName"], + [SecretSync.DigitalOceanAppPlatform]: ["appName"], + [SecretSync.Netlify]: ["accountName", "siteName"], + [SecretSync.Bitbucket]: [] +}; + +const defaultDuplicateCheck: DestinationDuplicateCheckFn = () => true; + +export const DESTINATION_DUPLICATE_CHECK_MAP: Record = { + [SecretSync.AWSParameterStore]: defaultDuplicateCheck, + [SecretSync.AWSSecretsManager]: defaultDuplicateCheck, + [SecretSync.GitHub]: defaultDuplicateCheck, + [SecretSync.GCPSecretManager]: defaultDuplicateCheck, + [SecretSync.AzureKeyVault]: defaultDuplicateCheck, + [SecretSync.AzureAppConfiguration]: defaultDuplicateCheck, + [SecretSync.AzureDevOps]: defaultDuplicateCheck, + [SecretSync.Databricks]: defaultDuplicateCheck, + [SecretSync.Humanitec]: defaultDuplicateCheck, + [SecretSync.TerraformCloud]: defaultDuplicateCheck, + [SecretSync.Camunda]: defaultDuplicateCheck, + [SecretSync.Vercel]: defaultDuplicateCheck, + [SecretSync.Windmill]: defaultDuplicateCheck, + [SecretSync.HCVault]: defaultDuplicateCheck, + [SecretSync.TeamCity]: defaultDuplicateCheck, + [SecretSync.OCIVault]: defaultDuplicateCheck, + [SecretSync.OnePass]: defaultDuplicateCheck, + [SecretSync.Heroku]: defaultDuplicateCheck, + [SecretSync.Render]: defaultDuplicateCheck, + [SecretSync.Flyio]: defaultDuplicateCheck, + [SecretSync.GitLab]: (existingConfig, newConfig) => { + const existingTargetEnv = existingConfig.targetEnvironment as string | undefined; + const newTargetEnv = newConfig.targetEnvironment as string | undefined; + + const wildcardValues = ["*", ""]; + // If either has wildcard, it conflicts with any targetEnvironment + if ( + !existingTargetEnv || + !newTargetEnv || + wildcardValues.includes(existingTargetEnv) || + wildcardValues.includes(newTargetEnv) + ) { + return true; + } + + return ( + existingTargetEnv === newTargetEnv && + ((newConfig.scope as string) === "group" + ? existingConfig.groupId === newConfig.groupId + : existingConfig.projectId === newConfig.projectId) + ); + }, + [SecretSync.CloudflarePages]: defaultDuplicateCheck, + [SecretSync.CloudflareWorkers]: defaultDuplicateCheck, + [SecretSync.Supabase]: defaultDuplicateCheck, + [SecretSync.Zabbix]: defaultDuplicateCheck, + [SecretSync.Railway]: defaultDuplicateCheck, + [SecretSync.Checkly]: defaultDuplicateCheck, + [SecretSync.DigitalOceanAppPlatform]: defaultDuplicateCheck, + [SecretSync.Netlify]: defaultDuplicateCheck, + [SecretSync.Bitbucket]: defaultDuplicateCheck +}; diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index c4b44c0a5..6a2f49386 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -37,7 +37,12 @@ import { import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretSyncDALFactory } from "./secret-sync-dal"; -import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps"; +import { + DESTINATION_DUPLICATE_CHECK_MAP, + SECRET_SYNC_CONNECTION_MAP, + SECRET_SYNC_NAME_MAP, + SECRET_SYNC_SKIP_FIELDS_MAP +} from "./secret-sync-maps"; import { TSecretSyncQueueFactory } from "./secret-sync-queue"; type TSecretSyncServiceFactoryDep = { @@ -698,83 +703,11 @@ export const secretSyncServiceFactory = ({ return updatedSecretSync as TSecretSync; }; - const getSkipFieldsForDestination = (destination: SecretSync): string[] => { - switch (destination) { - case SecretSync.AWSSecretsManager: - return ["mappingBehavior", "secretName"]; - case SecretSync.OnePass: - return ["valueLabel"]; - case SecretSync.AzureAppConfiguration: - return ["label"]; - case SecretSync.AzureDevOps: - return ["devopsProjectName"]; - case SecretSync.Checkly: - return ["groupName", "accountName"]; - case SecretSync.DigitalOceanAppPlatform: - return ["appName"]; - case SecretSync.GitLab: - return [ - "projectName", - "shouldProtectSecrets", - "shouldMaskSecrets", - "shouldHideSecrets", - "targetEnvironment", - "groupName", - "groupId", - "projectId" - ]; - case SecretSync.Heroku: - return ["appName"]; - case SecretSync.Netlify: - return ["accountName", "siteName"]; - case SecretSync.Railway: - return ["projectName", "environmentName", "serviceName"]; - case SecretSync.Supabase: - return ["projectName"]; - case SecretSync.TerraformCloud: - return ["variableSetName", "workspaceName"]; - case SecretSync.Vercel: - return ["appName"]; - case SecretSync.Zabbix: - return ["hostName", "macroType"]; - default: - return []; - } - }; - - const handleSpecialCaseDuplicateCheck = ( - destination: SecretSync, - existingConfig: Record, - newConfig: Record - ): boolean => { - switch (destination) { - case SecretSync.GitLab: { - const existingTargetEnv = existingConfig.targetEnvironment as string | undefined; - const newTargetEnv = newConfig.targetEnvironment as string | undefined; - - // If either has wildcard '*', it conflicts with any targetEnvironment - if (existingTargetEnv === "*" || newTargetEnv === "*") { - return true; - } - - return ( - existingTargetEnv === newTargetEnv && - ((newConfig.scope as string) === "group" - ? existingConfig.groupId === newConfig.groupId - : existingConfig.projectId === newConfig.projectId) - ); - } - default: - // For other sync types, no special handling needed - return true; - } - }; - const checkDuplicateDestination = async ( { destination, destinationConfig, excludeSyncId, projectId }: TCheckDuplicateDestinationDTO, actor: OrgServiceActor ) => { - const skipFields = getSkipFieldsForDestination(destination); + const skipFields = SECRET_SYNC_SKIP_FIELDS_MAP[destination]; const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, @@ -804,8 +737,7 @@ export const secretSyncServiceFactory = ({ try { const baseFieldsMatch = deepEqualSkipFields(sync.destinationConfig, destinationConfig, skipFields); if (baseFieldsMatch) { - return handleSpecialCaseDuplicateCheck( - destination, + return DESTINATION_DUPLICATE_CHECK_MAP[destination]( sync.destinationConfig as Record, destinationConfig ); diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index e742f4763..478c44c92 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -415,3 +415,8 @@ export type TSecretMap = Record< secretMetadata?: ResourceMetadataDTO; } >; + +export type DestinationDuplicateCheckFn = ( + existingConfig: Record, + newConfig: Record +) => boolean;