From f4779de05175464bcc97ed5b427f69161f497120 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 27 Jun 2025 14:03:59 -0300 Subject: [PATCH] feat(secret-sync): add re2 on replacements --- .../zabbix/zabbix-connection-fns.ts | 7 ++++-- .../secret-sync/zabbix/zabbix-sync-fns.ts | 22 ++++++++++++++----- docs/integrations/app-connections/zabbix.mdx | 2 +- docs/integrations/secret-syncs/zabbix.mdx | 4 ++-- .../ZabbixSyncReviewFields.tsx | 4 +++- .../ZabbixSyncDestinationSection.tsx | 4 +++- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/backend/src/services/app-connection/zabbix/zabbix-connection-fns.ts b/backend/src/services/app-connection/zabbix/zabbix-connection-fns.ts index 17839ee68..a34a6c381 100644 --- a/backend/src/services/app-connection/zabbix/zabbix-connection-fns.ts +++ b/backend/src/services/app-connection/zabbix/zabbix-connection-fns.ts @@ -1,4 +1,5 @@ import { AxiosError } from "axios"; +import RE2 from "re2"; import { request } from "@app/lib/config/request"; import { BadRequestError } from "@app/lib/errors"; @@ -13,6 +14,8 @@ import { TZabbixHostListResponse } from "./zabbix-connection-types"; +const TRAILING_SLASH_REGEX = new RE2("/+$"); + export const getZabbixConnectionListItem = () => { return { name: "Zabbix" as const, @@ -26,7 +29,7 @@ export const validateZabbixConnectionCredentials = async (config: TZabbixConnect await blockLocalAndPrivateIpAddresses(instanceUrl); try { - const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; + const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`; const payload = { jsonrpc: "2.0", @@ -66,7 +69,7 @@ export const listZabbixHosts = async (appConnection: TZabbixConnection): Promise await blockLocalAndPrivateIpAddresses(instanceUrl); try { - const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; + const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`; const payload = { jsonrpc: "2.0", diff --git a/backend/src/services/secret-sync/zabbix/zabbix-sync-fns.ts b/backend/src/services/secret-sync/zabbix/zabbix-sync-fns.ts index 6de70def8..a4048f312 100644 --- a/backend/src/services/secret-sync/zabbix/zabbix-sync-fns.ts +++ b/backend/src/services/secret-sync/zabbix/zabbix-sync-fns.ts @@ -1,3 +1,5 @@ +import RE2 from "re2"; + import { request } from "@app/lib/config/request"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; @@ -13,6 +15,14 @@ import { import { ZabbixSyncScope } from "./zabbix-sync-enums"; +const TRAILING_SLASH_REGEX = new RE2("/+$"); +const MACRO_START_REGEX = new RE2("^\\{\\$"); +const MACRO_END_REGEX = new RE2("\\}$"); + +const extractMacroKey = (macro: string): string => { + return macro.replace(MACRO_START_REGEX, "").replace(MACRO_END_REGEX, ""); +}; + // Helper function to handle Zabbix API responses and errors const handleZabbixResponse = (response: ZabbixApiResponse): T => { if (response.data.error) { @@ -34,7 +44,7 @@ const handleZabbixResponse = (response: ZabbixApiResponse): T => { }; const listZabbixSecrets = async (apiToken: string, instanceUrl: string, hostId?: string): Promise => { - const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; + const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`; const payload = { jsonrpc: "2.0" as const, @@ -66,7 +76,7 @@ const putZabbixSecrets = async ( destinationConfig: TZabbixSyncWithCredentials["destinationConfig"], existingSecrets: TZabbixSecret[] ): Promise => { - const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; + const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`; const hostId = destinationConfig.scope === ZabbixSyncScope.Host ? destinationConfig.hostId : undefined; const existingMacroMap = new Map(existingSecrets.map((secret) => [secret.macro, secret])); @@ -147,7 +157,7 @@ const deleteZabbixSecrets = async ( ): Promise => { if (keys.length === 0) return; - const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; + const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`; try { // Get existing macros to find their IDs @@ -214,7 +224,7 @@ export const ZabbixSyncFns = { .filter( (secret) => matchesSchema(secret.macro, environment?.slug || "", secretSync.syncOptions.keySchema) && - !shapedSecretMapKeys.includes(secret.macro.replace(/^\{\$/, "").replace(/\}$/, "")) + !shapedSecretMapKeys.includes(extractMacroKey(secret.macro)) ) .map((secret) => secret.macro); @@ -238,7 +248,7 @@ export const ZabbixSyncFns = { const shapedSecretMapKeys = Object.keys(secretMap).map((key) => key.toUpperCase()); const keys = secrets - .filter((secret) => shapedSecretMapKeys.includes(secret.macro.replace(/^\{\$/, "").replace(/\}$/, ""))) + .filter((secret) => shapedSecretMapKeys.includes(extractMacroKey(secret.macro))) .map((secret) => secret.macro); await deleteZabbixSecrets(apiToken, instanceUrl, keys, hostId); @@ -259,7 +269,7 @@ export const ZabbixSyncFns = { const secrets = await listZabbixSecrets(apiToken, instanceUrl, hostId); return Object.fromEntries( secrets.map((secret) => [ - secret.macro.replace(/^\{\$/, "").replace(/\}$/, ""), + extractMacroKey(secret.macro), { value: secret.value ?? "", comment: secret.description } ]) ); diff --git a/docs/integrations/app-connections/zabbix.mdx b/docs/integrations/app-connections/zabbix.mdx index e8bb09dd9..3a7bab85d 100644 --- a/docs/integrations/app-connections/zabbix.mdx +++ b/docs/integrations/app-connections/zabbix.mdx @@ -92,7 +92,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/ "app": "zabbix", "method": "api-token", "credentials": { - "instanceUrl": "https://zabbix.example.com", + "instanceUrl": "https://zabbix.example.com" } } } diff --git a/docs/integrations/secret-syncs/zabbix.mdx b/docs/integrations/secret-syncs/zabbix.mdx index e89e2ec02..4cfc0292d 100644 --- a/docs/integrations/secret-syncs/zabbix.mdx +++ b/docs/integrations/secret-syncs/zabbix.mdx @@ -37,8 +37,8 @@ description: "Learn how to configure a Zabbix Sync for Infisical." - **Zabbix Connection**: The Zabbix Connection to authenticate with. - **Scope**: The Zabbix scope to sync secrets to. - **Global**: Secrets will be synced globally. - - **Host**: Secrets will be synced to the specified host - - **Macro Type**: The type of macro to use when syncing secrets to Zabbix. + - **Host**: Secrets will be synced to the specified host. + - **Macro Type**: The type of macro to use when syncing secrets to Zabbix. Currently only **Text** and **Secret** macros are supported. The remaining fields are determined by the selected **Scope**: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ZabbixSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ZabbixSyncReviewFields.tsx index ecdaf3ac3..c58e35382 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ZabbixSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ZabbixSyncReviewFields.tsx @@ -5,6 +5,8 @@ import { GenericFieldLabel } from "@app/components/v2"; import { ZabbixSyncScope } from "@app/hooks/api/appConnections/zabbix"; import { SecretSync } from "@app/hooks/api/secretSyncs"; +const isTextMacro = (macroType: number) => macroType === 0; + export const ZabbixSyncReviewFields = () => { const { watch } = useFormContext(); const scope = watch("destinationConfig.scope"); @@ -22,7 +24,7 @@ export const ZabbixSyncReviewFields = () => { )} - {macroType === 0 ? "Text" : "Secret"} + {isTextMacro(macroType) ? "Text" : "Secret"} ); diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ZabbixSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ZabbixSyncDestinationSection.tsx index 4110697c5..13053c137 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ZabbixSyncDestinationSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ZabbixSyncDestinationSection.tsx @@ -6,6 +6,8 @@ type Props = { secretSync: TZabbixSync; }; +const isTextMacro = (macroType: number) => macroType === 0; + export const ZabbixSyncDestinationSection = ({ secretSync }: Props) => { const { destinationConfig: { macroType } @@ -24,7 +26,7 @@ export const ZabbixSyncDestinationSection = ({ secretSync }: Props) => { )} - {macroType === 0 ? "Text" : "Secret"} + {isTextMacro(macroType) ? "Text" : "Secret"} );