feat: added camunda secret sync

This commit is contained in:
Sheen Capadngan
2025-04-08 23:52:27 +08:00
parent 479d6445a7
commit 3f9a793578
34 changed files with 565 additions and 16 deletions
@@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const CAMUNDA_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Camunda",
destination: SecretSync.Camunda,
connection: AppConnection.Camunda,
canImportSecrets: true
};
@@ -0,0 +1,161 @@
import { request } from "@app/lib/config/request";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { getCamundaConnectionAccessToken } from "@app/services/app-connection/camunda";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import {
TCamundaCreateSecret,
TCamundaDeleteSecret,
TCamundaListSecrets,
TCamundaListSecretsResponse,
TCamundaPutSecret,
TCamundaSyncWithCredentials
} from "@app/services/secret-sync/camunda/camunda-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { TSecretMap } from "../secret-sync-types";
type TCamundaSecretSyncFactoryDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
const getCamundaSecrets = async ({ accessToken, clusterUUID }: TCamundaListSecrets) => {
const { data } = await request.get<TCamundaListSecretsResponse>(
`${IntegrationUrls.CAMUNDA_API_URL}/clusters/${clusterUUID}/secrets`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
return data;
};
const createCamundaSecret = async ({ accessToken, clusterUUID, key, value }: TCamundaCreateSecret) =>
request.post(
`${IntegrationUrls.CAMUNDA_API_URL}/clusters/${clusterUUID}/secrets`,
{
secretName: key,
secretValue: value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
const deleteCamundaSecret = async ({ accessToken, clusterUUID, key }: TCamundaDeleteSecret) =>
request.delete(`${IntegrationUrls.CAMUNDA_API_URL}/clusters/${clusterUUID}/secrets/${key}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
});
const updateCamundaSecret = async ({ accessToken, clusterUUID, key, value }: TCamundaPutSecret) =>
request.put(
`${IntegrationUrls.CAMUNDA_API_URL}/clusters/${clusterUUID}/secrets/${key}`,
{
secretValue: value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
export const camundaSyncFactory = ({ kmsService, appConnectionDAL }: TCamundaSecretSyncFactoryDeps) => {
const syncSecrets = async (secretSync: TCamundaSyncWithCredentials, secretMap: TSecretMap) => {
const {
destinationConfig: { clusterUUID },
connection
} = secretSync;
const accessToken = await getCamundaConnectionAccessToken(connection, appConnectionDAL, kmsService);
const camundaSecrets = await getCamundaSecrets({ accessToken, clusterUUID });
for await (const entry of Object.entries(secretMap)) {
const [key, { value }] = entry;
try {
if (camundaSecrets[key] === undefined) {
await createCamundaSecret({
key,
value,
clusterUUID,
accessToken
});
} else if (camundaSecrets[key] !== value) {
await updateCamundaSecret({
key,
value,
clusterUUID,
accessToken
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
if (secretSync.syncOptions.disableSecretDeletion) return;
for await (const secret of Object.keys(camundaSecrets)) {
if (!(secret in secretMap)) {
await deleteCamundaSecret({
key: secret,
clusterUUID,
accessToken
});
}
}
};
const removeSecrets = async (secretSync: TCamundaSyncWithCredentials, secretMap: TSecretMap) => {
const {
destinationConfig: { clusterUUID },
connection
} = secretSync;
const accessToken = await getCamundaConnectionAccessToken(connection, appConnectionDAL, kmsService);
const camundaSecrets = await getCamundaSecrets({ accessToken, clusterUUID });
for await (const secret of Object.keys(camundaSecrets)) {
if (!(secret in secretMap)) {
await deleteCamundaSecret({
key: secret,
clusterUUID,
accessToken
});
}
}
};
const getSecrets = async (secretSync: TCamundaSyncWithCredentials) => {
const {
destinationConfig: { clusterUUID },
connection
} = secretSync;
const accessToken = await getCamundaConnectionAccessToken(connection, appConnectionDAL, kmsService);
const camundaSecrets = await getCamundaSecrets({ accessToken, clusterUUID });
return Object.fromEntries(Object.entries(camundaSecrets).map(([key, value]) => [key, { value }]));
};
return {
syncSecrets,
removeSecrets,
getSecrets
};
};
@@ -0,0 +1,47 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const CamundaSyncDestinationConfigSchema = z.object({
scope: z.string().trim().min(1, "Camunda scope required").describe(SecretSyncs.DESTINATION_CONFIG.CAMUNDA.scope),
clusterUUID: z
.string()
.min(1, "Camunda cluster UUID is required")
.describe(SecretSyncs.DESTINATION_CONFIG.CAMUNDA.clusterUUID)
});
const CamundaSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
export const CamundaSyncSchema = BaseSecretSyncSchema(SecretSync.Camunda, CamundaSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Camunda),
destinationConfig: CamundaSyncDestinationConfigSchema
});
export const CreateCamundaSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Camunda,
CamundaSyncOptionsConfig
).extend({
destinationConfig: CamundaSyncDestinationConfigSchema
});
export const UpdateCamundaSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Camunda,
CamundaSyncOptionsConfig
).extend({
destinationConfig: CamundaSyncDestinationConfigSchema.optional()
});
export const CamundaSyncListItemSchema = z.object({
name: z.literal("Camunda"),
connection: z.literal(AppConnection.Camunda),
destination: z.literal(SecretSync.Camunda),
canImportSecrets: z.literal(true)
});
@@ -0,0 +1,38 @@
import { z } from "zod";
import { TCamundaConnection } from "@app/services/app-connection/camunda";
import { CamundaSyncListItemSchema, CamundaSyncSchema, CreateCamundaSyncSchema } from "./camunda-sync-schemas";
export type TCamundaSync = z.infer<typeof CamundaSyncSchema>;
export type TCamundaSyncInput = z.infer<typeof CreateCamundaSyncSchema>;
export type TCamundaSyncListItem = z.infer<typeof CamundaSyncListItemSchema>;
export type TCamundaSyncWithCredentials = TCamundaSync & {
connection: TCamundaConnection;
};
export type TCamundaListSecretsResponse = { [key: string]: string };
type TBaseCamundaSecretRequest = {
accessToken: string;
clusterUUID: string;
};
export type TCamundaListSecrets = TBaseCamundaSecretRequest;
export type TCamundaCreateSecret = {
key: string;
value?: string;
} & TBaseCamundaSecretRequest;
export type TCamundaPutSecret = {
key: string;
value?: string;
} & TBaseCamundaSecretRequest;
export type TCamundaDeleteSecret = {
key: string;
} & TBaseCamundaSecretRequest;
@@ -0,0 +1,4 @@
export * from "./camunda-sync-constants";
export * from "./camunda-sync-fns";
export * from "./camunda-sync-schemas";
export * from "./camunda-sync-types";
@@ -6,7 +6,8 @@ export enum SecretSync {
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration",
Databricks = "databricks",
Humanitec = "humanitec"
Humanitec = "humanitec",
Camunda = "camunda"
}
export enum SecretSyncInitialSyncBehavior {
@@ -22,6 +22,7 @@ import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
import { TKmsServiceFactory } from "../kms/kms-service";
import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFactory } from "./azure-app-configuration";
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
@@ -35,7 +36,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.AzureKeyVault]: AZURE_KEY_VAULT_SYNC_LIST_OPTION,
[SecretSync.AzureAppConfiguration]: AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION,
[SecretSync.Databricks]: DATABRICKS_SYNC_LIST_OPTION,
[SecretSync.Humanitec]: HUMANITEC_SYNC_LIST_OPTION
[SecretSync.Humanitec]: HUMANITEC_SYNC_LIST_OPTION,
[SecretSync.Camunda]: CAMUNDA_SYNC_LIST_OPTION
};
export const listSecretSyncOptions = () => {
@@ -121,6 +123,11 @@ export const SecretSyncFns = {
}).syncSecrets(secretSync, secretMap);
case SecretSync.Humanitec:
return HumanitecSyncFns.syncSecrets(secretSync, secretMap);
case SecretSync.Camunda:
return camundaSyncFactory({
appConnectionDAL,
kmsService
}).syncSecrets(secretSync, secretMap);
default:
throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -165,6 +172,12 @@ export const SecretSyncFns = {
case SecretSync.Humanitec:
secretMap = await HumanitecSyncFns.getSecrets(secretSync);
break;
case SecretSync.Camunda:
secretMap = await camundaSyncFactory({
appConnectionDAL,
kmsService
}).getSecrets(secretSync);
break;
default:
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -207,6 +220,11 @@ export const SecretSyncFns = {
}).removeSecrets(secretSync, secretMap);
case SecretSync.Humanitec:
return HumanitecSyncFns.removeSecrets(secretSync, secretMap);
case SecretSync.Camunda:
return camundaSyncFactory({
appConnectionDAL,
kmsService
}).removeSecrets(secretSync, secretMap);
default:
throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -9,7 +9,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.AzureKeyVault]: "Azure Key Vault",
[SecretSync.AzureAppConfiguration]: "Azure App Configuration",
[SecretSync.Databricks]: "Databricks",
[SecretSync.Humanitec]: "Humanitec"
[SecretSync.Humanitec]: "Humanitec",
[SecretSync.Camunda]: "Camunda"
};
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@@ -20,5 +21,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.AzureKeyVault]: AppConnection.AzureKeyVault,
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration,
[SecretSync.Databricks]: AppConnection.Databricks,
[SecretSync.Humanitec]: AppConnection.Humanitec
[SecretSync.Humanitec]: AppConnection.Humanitec,
[SecretSync.Camunda]: AppConnection.Camunda
};
@@ -9,6 +9,12 @@ import {
TAwsSecretsManagerSyncListItem,
TAwsSecretsManagerSyncWithCredentials
} from "@app/services/secret-sync/aws-secrets-manager";
import {
TCamundaSync,
TCamundaSyncInput,
TCamundaSyncListItem,
TCamundaSyncWithCredentials
} from "@app/services/secret-sync/camunda";
import {
TDatabricksSync,
TDatabricksSyncInput,
@@ -58,7 +64,8 @@ export type TSecretSync =
| TAzureKeyVaultSync
| TAzureAppConfigurationSync
| TDatabricksSync
| THumanitecSync;
| THumanitecSync
| TCamundaSync;
export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials
@@ -68,7 +75,8 @@ export type TSecretSyncWithCredentials =
| TAzureKeyVaultSyncWithCredentials
| TAzureAppConfigurationSyncWithCredentials
| TDatabricksSyncWithCredentials
| THumanitecSyncWithCredentials;
| THumanitecSyncWithCredentials
| TCamundaSyncWithCredentials;
export type TSecretSyncInput =
| TAwsParameterStoreSyncInput
@@ -78,7 +86,8 @@ export type TSecretSyncInput =
| TAzureKeyVaultSyncInput
| TAzureAppConfigurationSyncInput
| TDatabricksSyncInput
| THumanitecSyncInput;
| THumanitecSyncInput
| TCamundaSyncInput;
export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem
@@ -88,7 +97,8 @@ export type TSecretSyncListItem =
| TAzureKeyVaultSyncListItem
| TAzureAppConfigurationSyncListItem
| TDatabricksSyncListItem
| THumanitecSyncListItem;
| THumanitecSyncListItem
| TCamundaSyncListItem;
export type TSyncOptionsConfig = {
canImportSecrets: boolean;