final release

This commit is contained in:
x032205
2025-05-14 01:16:42 -04:00
parent bae62421ae
commit 63f0f8e299
22 changed files with 93 additions and 153 deletions
@@ -367,17 +367,7 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
querystring: z.object({ querystring: z.object({
importBehavior: z importBehavior: z
.nativeEnum(SecretSyncImportBehavior) .nativeEnum(SecretSyncImportBehavior)
.describe(SecretSyncs.IMPORT_SECRETS(destination).importBehavior), .describe(SecretSyncs.IMPORT_SECRETS(destination).importBehavior)
filterForSchema: z
.enum(["true", "false"])
.optional()
.default("false")
.transform((v) => v === "true"),
stripSchema: z
.enum(["true", "false"])
.optional()
.default("false")
.transform((v) => v === "true")
}), }),
response: { response: {
200: z.object({ secretSync: responseSchema }) 200: z.object({ secretSync: responseSchema })
@@ -386,15 +376,13 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const { syncId } = req.params; const { syncId } = req.params;
const { importBehavior, filterForSchema, stripSchema } = req.query; const { importBehavior } = req.query;
const secretSync = (await server.services.secretSync.triggerSecretSyncImportSecretsById( const secretSync = (await server.services.secretSync.triggerSecretSyncImportSecretsById(
{ {
syncId, syncId,
destination, destination,
importBehavior, importBehavior
filterForSchema,
stripSchema
}, },
req.permission req.permission
)) as T; )) as T;
@@ -2,6 +2,7 @@ import AWS, { AWSError } from "aws-sdk";
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns"; import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { TAwsParameterStoreSyncWithCredentials } from "./aws-parameter-store-sync-types"; import { TAwsParameterStoreSyncWithCredentials } from "./aws-parameter-store-sync-types";
@@ -389,6 +390,9 @@ export const AwsParameterStoreSyncFns = {
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) { for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
const [key, parameter] = entry; const [key, parameter] = entry;
// eslint-disable-next-line no-continue
if (!matchesSchema(key, syncOptions.keySchema)) continue;
if (!(key in secretMap) || !secretMap[key].value) { if (!(key in secretMap) || !secretMap[key].value) {
parametersToDelete.push(parameter); parametersToDelete.push(parameter);
} }
@@ -27,6 +27,7 @@ import {
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns"; import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums"; import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { TAwsSecretsManagerSyncWithCredentials } from "./aws-secrets-manager-sync-types"; import { TAwsSecretsManagerSyncWithCredentials } from "./aws-secrets-manager-sync-types";
@@ -399,6 +400,9 @@ export const AwsSecretsManagerSyncFns = {
if (syncOptions.disableSecretDeletion) return; if (syncOptions.disableSecretDeletion) return;
for await (const secretKey of Object.keys(awsSecretsRecord)) { for await (const secretKey of Object.keys(awsSecretsRecord)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(secretKey, syncOptions.keySchema)) continue;
if (!(secretKey in secretMap) || !secretMap[secretKey].value) { if (!(secretKey in secretMap) || !secretMap[secretKey].value) {
try { try {
await deleteSecret(client, secretKey); await deleteSecret(client, secretKey);
@@ -7,6 +7,7 @@ import { TAppConnectionDALFactory } from "@app/services/app-connection/app-conne
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault"; import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
import { isAzureKeyVaultReference } from "@app/services/integration-auth/integration-sync-secret-fns"; import { isAzureKeyVaultReference } from "@app/services/integration-auth/integration-sync-secret-fns";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { TAzureAppConfigurationSyncWithCredentials } from "./azure-app-configuration-sync-types"; import { TAzureAppConfigurationSyncWithCredentials } from "./azure-app-configuration-sync-types";
@@ -139,6 +140,9 @@ export const azureAppConfigurationSyncFactory = ({
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const key of Object.keys(azureAppConfigSecrets)) { for await (const key of Object.keys(azureAppConfigSecrets)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue;
const azureSecret = azureAppConfigSecrets[key]; const azureSecret = azureAppConfigSecrets[key];
if ( if (
!(key in secretMap) || !(key in secretMap) ||
@@ -5,6 +5,7 @@ import { request } from "@app/lib/config/request";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault"; import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { SecretSyncError } from "../secret-sync-errors"; import { SecretSyncError } from "../secret-sync-errors";
@@ -192,7 +193,9 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const deleteSecretKey of deleteSecrets.filter( for await (const deleteSecretKey of deleteSecrets.filter(
(secret) => !setSecrets.find((setSecret) => setSecret.key === secret) (secret) =>
matchesSchema(secret, secretSync.syncOptions.keySchema) &&
!setSecrets.find((setSecret) => setSecret.key === secret)
)) { )) {
await request.delete(`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${deleteSecretKey}?api-version=7.3`, { await request.delete(`${secretSync.destinationConfig.vaultBaseUrl}/secrets/${deleteSecretKey}?api-version=7.3`, {
headers: { headers: {
@@ -12,6 +12,7 @@ import {
TCamundaSyncWithCredentials TCamundaSyncWithCredentials
} from "@app/services/secret-sync/camunda/camunda-sync-types"; } from "@app/services/secret-sync/camunda/camunda-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "../secret-sync-types"; import { TSecretMap } from "../secret-sync-types";
@@ -116,6 +117,9 @@ export const camundaSyncFactory = ({ kmsService, appConnectionDAL }: TCamundaSec
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const secret of Object.keys(camundaSecrets)) { for await (const secret of Object.keys(camundaSecrets)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(secret, secretSync.syncOptions.keySchema)) continue;
if (!(secret in secretMap) || !secretMap[secret].value) { if (!(secret in secretMap) || !secretMap[secret].value) {
try { try {
await deleteCamundaSecret({ await deleteCamundaSecret({
@@ -11,6 +11,7 @@ import {
TDatabricksSyncWithCredentials TDatabricksSyncWithCredentials
} from "@app/services/secret-sync/databricks/databricks-sync-types"; } from "@app/services/secret-sync/databricks/databricks-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { TSecretMap } from "../secret-sync-types"; import { TSecretMap } from "../secret-sync-types";
@@ -115,6 +116,9 @@ export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabri
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const secret of databricksSecretKeys) { for await (const secret of databricksSecretKeys) {
// eslint-disable-next-line no-continue
if (!matchesSchema(secret.key, secretSync.syncOptions.keySchema)) continue;
if (!(secret.key in secretMap)) { if (!(secret.key in secretMap)) {
await deleteDatabricksSecrets({ await deleteDatabricksSecrets({
key: secret.key, key: secret.key,
@@ -4,6 +4,7 @@ import { request } from "@app/lib/config/request";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { getGcpConnectionAuthToken } from "@app/services/app-connection/gcp"; import { getGcpConnectionAuthToken } from "@app/services/app-connection/gcp";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { SecretSyncError } from "../secret-sync-errors"; import { SecretSyncError } from "../secret-sync-errors";
import { TSecretMap } from "../secret-sync-types"; import { TSecretMap } from "../secret-sync-types";
@@ -153,6 +154,9 @@ export const GcpSyncFns = {
} }
for await (const key of Object.keys(gcpSecrets)) { for await (const key of Object.keys(gcpSecrets)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue;
try { try {
if (!(key in secretMap) || !secretMap[key].value) { if (!(key in secretMap) || !secretMap[key].value) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
@@ -4,6 +4,7 @@ import sodium from "libsodium-wrappers";
import { getGitHubClient } from "@app/services/app-connection/github"; import { getGitHubClient } from "@app/services/app-connection/github";
import { GitHubSyncScope, GitHubSyncVisibility } from "@app/services/secret-sync/github/github-sync-enums"; import { GitHubSyncScope, GitHubSyncVisibility } from "@app/services/secret-sync/github/github-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
@@ -222,6 +223,9 @@ export const GithubSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const encryptedSecret of encryptedSecrets) { for await (const encryptedSecret of encryptedSecrets) {
// eslint-disable-next-line no-continue
if (!matchesSchema(encryptedSecret.name, secretSync.syncOptions.keySchema)) continue;
if (!(encryptedSecret.name in secretMap)) { if (!(encryptedSecret.name in secretMap)) {
await deleteSecret(client, secretSync, encryptedSecret); await deleteSecret(client, secretSync, encryptedSecret);
} }
@@ -11,6 +11,7 @@ import {
TPostHCVaultVariable TPostHCVaultVariable
} from "@app/services/secret-sync/hc-vault/hc-vault-sync-types"; } from "@app/services/secret-sync/hc-vault/hc-vault-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
const listHCVaultVariables = async ({ instanceUrl, namespace, mount, accessToken, path }: THCVaultListVariables) => { const listHCVaultVariables = async ({ instanceUrl, namespace, mount, accessToken, path }: THCVaultListVariables) => {
@@ -68,7 +69,7 @@ export const HCVaultSyncFns = {
const { const {
connection, connection,
destinationConfig: { mount, path }, destinationConfig: { mount, path },
syncOptions: { disableSecretDeletion } syncOptions: { disableSecretDeletion, keySchema }
} = secretSync; } = secretSync;
const { namespace } = connection.credentials; const { namespace } = connection.credentials;
@@ -95,6 +96,9 @@ export const HCVaultSyncFns = {
if (disableSecretDeletion) return; if (disableSecretDeletion) return;
for await (const [key] of Object.entries(variables)) { for await (const [key] of Object.entries(variables)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
delete variables[key]; delete variables[key];
tainted = true; tainted = true;
@@ -2,6 +2,7 @@ import { request } from "@app/lib/config/request";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
@@ -199,6 +200,9 @@ export const HumanitecSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const humanitecSecret of humanitecSecrets) { for await (const humanitecSecret of humanitecSecrets) {
// eslint-disable-next-line no-continue
if (!matchesSchema(humanitecSecret.key, secretSync.syncOptions.keySchema)) continue;
if (!secretMap[humanitecSecret.key]) { if (!secretMap[humanitecSecret.key]) {
await deleteSecret(secretSync, humanitecSecret); await deleteSecret(secretSync, humanitecSecret);
} }
@@ -11,6 +11,7 @@ import {
TUpdateOCIVaultVariable TUpdateOCIVaultVariable
} from "@app/services/secret-sync/oci-vault/oci-vault-sync-types"; } from "@app/services/secret-sync/oci-vault/oci-vault-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
const listOCIVaultVariables = async ({ provider, compartmentId, vaultId, onlyActive }: TOCIVaultListVariables) => { const listOCIVaultVariables = async ({ provider, compartmentId, vaultId, onlyActive }: TOCIVaultListVariables) => {
@@ -211,6 +212,9 @@ export const OCIVaultSyncFns = {
// Update and delete secrets // Update and delete secrets
for await (const [key, variable] of Object.entries(variables)) { for await (const [key, variable] of Object.entries(variables)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue;
// Only update / delete active secrets // Only update / delete active secrets
if (variable.lifecycleState === vault.models.SecretSummary.LifecycleState.Active) { if (variable.lifecycleState === vault.models.SecretSummary.LifecycleState.Active) {
if (key in secretMap && secretMap[key].value.length > 0) { if (key in secretMap && secretMap[key].value.length > 0) {
@@ -61,11 +61,6 @@ type TSyncSecretDeps = {
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
}; };
interface TSyncSecretConfig {
filterForSchema?: boolean;
stripSchema?: boolean;
}
// Add schema to secret keys // Add schema to secret keys
const addSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMap => { const addSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecretMap => {
if (!schema) return unprocessedSecretMap; if (!schema) return unprocessedSecretMap;
@@ -101,17 +96,22 @@ const stripSchema = (unprocessedSecretMap: TSecretMap, schema?: string): TSecret
return strippedMap; return strippedMap;
}; };
// Filter only for secrets with keys that match the schema // Checks if a key matches a schema
const filterForSchema = (secretMap: TSecretMap, schema?: string): TSecretMap => { export const matchesSchema = (key: string, schema?: string): boolean => {
if (!schema) return secretMap; if (!schema) return true;
const [prefix, suffix] = schema.split("{{secretKey}}"); const [prefix, suffix] = schema.split("{{secretKey}}");
if (prefix === undefined || suffix === undefined) return secretMap; if (prefix === undefined || suffix === undefined) return true;
return key.startsWith(prefix) && key.endsWith(suffix);
};
// Filter only for secrets with keys that match the schema
const filterForSchema = (secretMap: TSecretMap, schema?: string): TSecretMap => {
const filteredMap: TSecretMap = {}; const filteredMap: TSecretMap = {};
for (const [key, value] of Object.entries(secretMap)) { for (const [key, value] of Object.entries(secretMap)) {
if (key.startsWith(prefix) && key.endsWith(suffix)) { if (matchesSchema(key, schema)) {
filteredMap[key] = value; filteredMap[key] = value;
} }
} }
@@ -178,8 +178,7 @@ export const SecretSyncFns = {
}, },
getSecrets: async ( getSecrets: async (
secretSync: TSecretSyncWithCredentials, secretSync: TSecretSyncWithCredentials,
{ kmsService, appConnectionDAL }: TSyncSecretDeps, { kmsService, appConnectionDAL }: TSyncSecretDeps
config?: TSyncSecretConfig
): Promise<TSecretMap> => { ): Promise<TSecretMap> => {
let secretMap: TSecretMap; let secretMap: TSecretMap;
switch (secretSync.destination) { switch (secretSync.destination) {
@@ -245,17 +244,7 @@ export const SecretSyncFns = {
); );
} }
let processedSecretMap = secretMap; return stripSchema(filterForSchema(secretMap), secretSync.syncOptions.keySchema);
if (config?.filterForSchema) {
processedSecretMap = filterForSchema(processedSecretMap);
}
if (config?.stripSchema) {
return stripSchema(processedSecretMap, secretSync.syncOptions.keySchema);
}
return processedSecretMap;
}, },
removeSecrets: ( removeSecrets: (
secretSync: TSecretSyncWithCredentials, secretSync: TSecretSyncWithCredentials,
@@ -319,12 +319,9 @@ export const secretSyncQueueFactory = ({
); );
}; };
// TODO(andrey): Possibly add a "stripSchema" parameter for imports?
const $importSecrets = async ( const $importSecrets = async (
secretSync: TSecretSyncWithCredentials, secretSync: TSecretSyncWithCredentials,
importBehavior: SecretSyncImportBehavior, importBehavior: SecretSyncImportBehavior
filterForSchema: boolean,
stripSchema: boolean
): Promise<TSecretMap> => { ): Promise<TSecretMap> => {
const { projectId, environment, folder } = secretSync; const { projectId, environment, folder } = secretSync;
@@ -333,17 +330,10 @@ export const secretSyncQueueFactory = ({
"Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path." "Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path."
); );
const importedSecrets = await SecretSyncFns.getSecrets( const importedSecrets = await SecretSyncFns.getSecrets(secretSync, {
secretSync,
{
appConnectionDAL, appConnectionDAL,
kmsService kmsService
}, });
{
filterForSchema,
stripSchema
}
);
if (!Object.keys(importedSecrets).length) return {}; if (!Object.keys(importedSecrets).length) return {};
@@ -454,9 +444,7 @@ export const secretSyncQueueFactory = ({
secretSyncWithCredentials, secretSyncWithCredentials,
initialSyncBehavior === SecretSyncInitialSyncBehavior.ImportPrioritizeSource initialSyncBehavior === SecretSyncInitialSyncBehavior.ImportPrioritizeSource
? SecretSyncImportBehavior.PrioritizeSource ? SecretSyncImportBehavior.PrioritizeSource
: SecretSyncImportBehavior.PrioritizeDestination, : SecretSyncImportBehavior.PrioritizeDestination
false,
false
); );
Object.entries(importedSecretMap).forEach(([key, secretData]) => { Object.entries(importedSecretMap).forEach(([key, secretData]) => {
@@ -548,7 +536,7 @@ export const secretSyncQueueFactory = ({
const $handleImportSecretsJob = async (job: TSecretSyncImportSecretsDTO) => { const $handleImportSecretsJob = async (job: TSecretSyncImportSecretsDTO) => {
const { const {
data: { syncId, auditLogInfo, importBehavior, filterForSchema, stripSchema } data: { syncId, auditLogInfo, importBehavior }
} = job; } = job;
const secretSync = await secretSyncDAL.findById(syncId); const secretSync = await secretSyncDAL.findById(syncId);
@@ -586,9 +574,7 @@ export const secretSyncQueueFactory = ({
credentials credentials
} }
} as TSecretSyncWithCredentials, } as TSecretSyncWithCredentials,
importBehavior, importBehavior
filterForSchema,
stripSchema
); );
isSuccess = true; isSuccess = true;
@@ -231,8 +231,6 @@ export type TQueueSecretSyncImportSecretsByIdDTO = {
syncId: string; syncId: string;
importBehavior: SecretSyncImportBehavior; importBehavior: SecretSyncImportBehavior;
auditLogInfo?: AuditLogInfo; auditLogInfo?: AuditLogInfo;
filterForSchema: boolean;
stripSchema: boolean;
}; };
export type TTriggerSecretSyncImportSecretsByIdDTO = { export type TTriggerSecretSyncImportSecretsByIdDTO = {
@@ -1,6 +1,7 @@
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { getTeamCityInstanceUrl } from "@app/services/app-connection/teamcity"; import { getTeamCityInstanceUrl } from "@app/services/app-connection/teamcity";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { import {
TDeleteTeamCityVariable, TDeleteTeamCityVariable,
@@ -125,6 +126,9 @@ export const TeamCitySyncFns = {
const variables = await listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig }); const variables = await listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig });
for await (const [key, variable] of Object.entries(variables)) { for await (const [key, variable] of Object.entries(variables)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, secretSync.syncOptions.keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
try { try {
await deleteTeamCityVariable({ await deleteTeamCityVariable({
@@ -4,6 +4,7 @@ import { AxiosResponse } from "axios";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
@@ -231,6 +232,9 @@ export const TerraformCloudSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for (const terraformCloudVariable of terraformCloudVariables) { for (const terraformCloudVariable of terraformCloudVariables) {
// eslint-disable-next-line no-continue
if (!matchesSchema(terraformCloudVariable.key, secretSync.syncOptions.keySchema)) continue;
if (!Object.prototype.hasOwnProperty.call(secretMap, terraformCloudVariable.key)) { if (!Object.prototype.hasOwnProperty.call(secretMap, terraformCloudVariable.key)) {
await deleteVariable(secretSync, terraformCloudVariable); await deleteVariable(secretSync, terraformCloudVariable);
} }
@@ -2,6 +2,7 @@
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { VercelEnvironmentType } from "./vercel-sync-enums"; import { VercelEnvironmentType } from "./vercel-sync-enums";
@@ -290,6 +291,9 @@ export const VercelSyncFns = {
if (secretSync.syncOptions.disableSecretDeletion) return; if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const vercelSecret of vercelSecrets) { for await (const vercelSecret of vercelSecrets) {
// eslint-disable-next-line no-continue
if (!matchesSchema(vercelSecret.key, secretSync.syncOptions.keySchema)) continue;
if (!secretMap[vercelSecret.key]) { if (!secretMap[vercelSecret.key]) {
await deleteSecret(secretSync, vercelSecret); await deleteSecret(secretSync, vercelSecret);
} }
@@ -1,6 +1,7 @@
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { getWindmillInstanceUrl } from "@app/services/app-connection/windmill"; import { getWindmillInstanceUrl } from "@app/services/app-connection/windmill";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { import {
TDeleteWindmillVariable, TDeleteWindmillVariable,
TPostWindmillVariable, TPostWindmillVariable,
@@ -128,7 +129,7 @@ export const WindmillSyncFns = {
const { const {
connection, connection,
destinationConfig: { path }, destinationConfig: { path },
syncOptions: { disableSecretDeletion } syncOptions: { disableSecretDeletion, keySchema }
} = secretSync; } = secretSync;
// url needs to be lowercase // url needs to be lowercase
@@ -169,6 +170,9 @@ export const WindmillSyncFns = {
if (disableSecretDeletion) return; if (disableSecretDeletion) return;
for await (const [key, variable] of Object.entries(variables)) { for await (const [key, variable] of Object.entries(variables)) {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, keySchema)) continue;
if (!(key in secretMap)) { if (!(key in secretMap)) {
try { try {
await deleteWindmillVariable({ await deleteWindmillVariable({
@@ -10,8 +10,7 @@ import {
ModalClose, ModalClose,
ModalContent, ModalContent,
Select, Select,
SelectItem, SelectItem
Switch
} from "@app/components/v2"; } from "@app/components/v2";
import { SECRET_SYNC_IMPORT_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SECRET_SYNC_IMPORT_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { import {
@@ -32,20 +31,13 @@ type ContentProps = {
}; };
const FormSchema = z.object({ const FormSchema = z.object({
importBehavior: z.nativeEnum(SecretSyncImportBehavior), importBehavior: z.nativeEnum(SecretSyncImportBehavior)
filterForSchema: z.boolean(),
stripSchema: z.boolean()
}); });
type TFormData = z.infer<typeof FormSchema>; type TFormData = z.infer<typeof FormSchema>;
const Content = ({ secretSync, onComplete }: ContentProps) => { const Content = ({ secretSync, onComplete }: ContentProps) => {
const { const { id: syncId, destination, projectId } = secretSync;
id: syncId,
destination,
projectId,
syncOptions: { keySchema }
} = secretSync;
const destinationName = SECRET_SYNC_MAP[destination].name; const destinationName = SECRET_SYNC_MAP[destination].name;
const { const {
@@ -53,28 +45,18 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
control, control,
formState: { isSubmitting, isDirty } formState: { isSubmitting, isDirty }
} = useForm<TFormData>({ } = useForm<TFormData>({
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema)
defaultValues: {
filterForSchema: false,
stripSchema: false
}
}); });
const triggerImportSecrets = useTriggerSecretSyncImportSecrets(); const triggerImportSecrets = useTriggerSecretSyncImportSecrets();
const handleTriggerImportSecrets = async ({ const handleTriggerImportSecrets = async ({ importBehavior }: TFormData) => {
importBehavior,
filterForSchema,
stripSchema
}: TFormData) => {
try { try {
await triggerImportSecrets.mutateAsync({ await triggerImportSecrets.mutateAsync({
syncId, syncId,
destination, destination,
importBehavior, importBehavior,
projectId, projectId
filterForSchema,
stripSchema
}); });
createNotification({ createNotification({
@@ -151,64 +133,6 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
</FormControl> </FormControl>
)} )}
/> />
{keySchema && (
<>
<Controller
control={control}
name="filterForSchema"
defaultValue
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-md"
tooltipText={
<div className="flex flex-col gap-2">
<p>
If enabled, Infisical will only import destination secrets that match your key
schema:
</p>
<code className="text-mineshaft-300">{keySchema}</code>
</div>
}
label="Filter Keys for Schema"
isError={Boolean(error)}
errorText={error?.message}
>
<Switch
id="filter-for-schema"
thumbClassName="bg-mineshaft-800"
isChecked={value}
onCheckedChange={onChange}
>
Only import destination secrets that match schema
</Switch>
</FormControl>
)}
/>
<Controller
control={control}
name="stripSchema"
defaultValue
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-md"
tooltipText="If enabled, Infisical will strip secret keys according to your schema. Keys that do not match the schema will not be affected."
label="Strip Schema"
isError={Boolean(error)}
errorText={error?.message}
>
<Switch
id="strip-schema"
thumbClassName="bg-mineshaft-800"
isChecked={value}
onCheckedChange={onChange}
>
Strip schema from imported secret keys
</Switch>
</FormControl>
)}
/>
</>
)}
<div className="mt-8 flex w-full items-center justify-between gap-2"> <div className="mt-8 flex w-full items-center justify-between gap-2">
<ModalClose asChild> <ModalClose asChild>
<Button colorSchema="secondary" variant="plain"> <Button colorSchema="secondary" variant="plain">
@@ -86,12 +86,10 @@ export const useTriggerSecretSyncImportSecrets = () => {
mutationFn: async ({ mutationFn: async ({
syncId, syncId,
destination, destination,
importBehavior, importBehavior
filterForSchema,
stripSchema
}: TTriggerSecretSyncImportSecretsDTO) => { }: TTriggerSecretSyncImportSecretsDTO) => {
const { data } = await apiRequest.post( const { data } = await apiRequest.post(
`/api/v1/secret-syncs/${destination}/${syncId}/import-secrets?importBehavior=${importBehavior}&filterForSchema=${filterForSchema}&stripSchema=${stripSchema}` `/api/v1/secret-syncs/${destination}/${syncId}/import-secrets?importBehavior=${importBehavior}`
); );
return data; return data;
@@ -82,8 +82,6 @@ export type TTriggerSecretSyncImportSecretsDTO = {
syncId: string; syncId: string;
importBehavior: SecretSyncImportBehavior; importBehavior: SecretSyncImportBehavior;
projectId: string; projectId: string;
filterForSchema: boolean;
stripSchema: boolean;
}; };
export type TTriggerSecretSyncRemoveSecretsDTO = { export type TTriggerSecretSyncRemoveSecretsDTO = {