Improve types and maps for secret sync warning logic

This commit is contained in:
Carlos Monastyrski
2025-10-06 22:39:53 -03:00
parent fb1d5ca257
commit 0292788c2c
3 changed files with 112 additions and 76 deletions

View File

@@ -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, string> = {
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
@@ -99,3 +100,101 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.Netlify]: SecretSyncPlanType.Regular,
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular
};
export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
[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, DestinationDuplicateCheckFn> = {
[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
};

View File

@@ -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<string, unknown>,
newConfig: Record<string, unknown>
): 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<string, unknown>,
destinationConfig
);

View File

@@ -415,3 +415,8 @@ export type TSecretMap = Record<
secretMetadata?: ResourceMetadataDTO;
}
>;
export type DestinationDuplicateCheckFn = (
existingConfig: Record<string, unknown>,
newConfig: Record<string, unknown>
) => boolean;