feat(secret-sync): add re2 on replacements

This commit is contained in:
carlosmonastyrski
2025-06-27 14:03:59 -03:00
parent defe7b8f0b
commit f4779de051
6 changed files with 30 additions and 13 deletions
@@ -1,4 +1,5 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import RE2 from "re2";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
@@ -13,6 +14,8 @@ import {
TZabbixHostListResponse TZabbixHostListResponse
} from "./zabbix-connection-types"; } from "./zabbix-connection-types";
const TRAILING_SLASH_REGEX = new RE2("/+$");
export const getZabbixConnectionListItem = () => { export const getZabbixConnectionListItem = () => {
return { return {
name: "Zabbix" as const, name: "Zabbix" as const,
@@ -26,7 +29,7 @@ export const validateZabbixConnectionCredentials = async (config: TZabbixConnect
await blockLocalAndPrivateIpAddresses(instanceUrl); await blockLocalAndPrivateIpAddresses(instanceUrl);
try { try {
const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`;
const payload = { const payload = {
jsonrpc: "2.0", jsonrpc: "2.0",
@@ -66,7 +69,7 @@ export const listZabbixHosts = async (appConnection: TZabbixConnection): Promise
await blockLocalAndPrivateIpAddresses(instanceUrl); await blockLocalAndPrivateIpAddresses(instanceUrl);
try { try {
const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`;
const payload = { const payload = {
jsonrpc: "2.0", jsonrpc: "2.0",
@@ -1,3 +1,5 @@
import RE2 from "re2";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
@@ -13,6 +15,14 @@ import {
import { ZabbixSyncScope } from "./zabbix-sync-enums"; 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 // Helper function to handle Zabbix API responses and errors
const handleZabbixResponse = <T>(response: ZabbixApiResponse<T>): T => { const handleZabbixResponse = <T>(response: ZabbixApiResponse<T>): T => {
if (response.data.error) { if (response.data.error) {
@@ -34,7 +44,7 @@ const handleZabbixResponse = <T>(response: ZabbixApiResponse<T>): T => {
}; };
const listZabbixSecrets = async (apiToken: string, instanceUrl: string, hostId?: string): Promise<TZabbixSecret[]> => { const listZabbixSecrets = async (apiToken: string, instanceUrl: string, hostId?: string): Promise<TZabbixSecret[]> => {
const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`;
const payload = { const payload = {
jsonrpc: "2.0" as const, jsonrpc: "2.0" as const,
@@ -66,7 +76,7 @@ const putZabbixSecrets = async (
destinationConfig: TZabbixSyncWithCredentials["destinationConfig"], destinationConfig: TZabbixSyncWithCredentials["destinationConfig"],
existingSecrets: TZabbixSecret[] existingSecrets: TZabbixSecret[]
): Promise<void> => { ): Promise<void> => {
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 hostId = destinationConfig.scope === ZabbixSyncScope.Host ? destinationConfig.hostId : undefined;
const existingMacroMap = new Map(existingSecrets.map((secret) => [secret.macro, secret])); const existingMacroMap = new Map(existingSecrets.map((secret) => [secret.macro, secret]));
@@ -147,7 +157,7 @@ const deleteZabbixSecrets = async (
): Promise<void> => { ): Promise<void> => {
if (keys.length === 0) return; if (keys.length === 0) return;
const apiUrl = `${instanceUrl.replace(/\/$/, "")}/api_jsonrpc.php`; const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`;
try { try {
// Get existing macros to find their IDs // Get existing macros to find their IDs
@@ -214,7 +224,7 @@ export const ZabbixSyncFns = {
.filter( .filter(
(secret) => (secret) =>
matchesSchema(secret.macro, environment?.slug || "", secretSync.syncOptions.keySchema) && matchesSchema(secret.macro, environment?.slug || "", secretSync.syncOptions.keySchema) &&
!shapedSecretMapKeys.includes(secret.macro.replace(/^\{\$/, "").replace(/\}$/, "")) !shapedSecretMapKeys.includes(extractMacroKey(secret.macro))
) )
.map((secret) => secret.macro); .map((secret) => secret.macro);
@@ -238,7 +248,7 @@ export const ZabbixSyncFns = {
const shapedSecretMapKeys = Object.keys(secretMap).map((key) => key.toUpperCase()); const shapedSecretMapKeys = Object.keys(secretMap).map((key) => key.toUpperCase());
const keys = secrets const keys = secrets
.filter((secret) => shapedSecretMapKeys.includes(secret.macro.replace(/^\{\$/, "").replace(/\}$/, ""))) .filter((secret) => shapedSecretMapKeys.includes(extractMacroKey(secret.macro)))
.map((secret) => secret.macro); .map((secret) => secret.macro);
await deleteZabbixSecrets(apiToken, instanceUrl, keys, hostId); await deleteZabbixSecrets(apiToken, instanceUrl, keys, hostId);
@@ -259,7 +269,7 @@ export const ZabbixSyncFns = {
const secrets = await listZabbixSecrets(apiToken, instanceUrl, hostId); const secrets = await listZabbixSecrets(apiToken, instanceUrl, hostId);
return Object.fromEntries( return Object.fromEntries(
secrets.map((secret) => [ secrets.map((secret) => [
secret.macro.replace(/^\{\$/, "").replace(/\}$/, ""), extractMacroKey(secret.macro),
{ value: secret.value ?? "", comment: secret.description } { value: secret.value ?? "", comment: secret.description }
]) ])
); );
+1 -1
View File
@@ -92,7 +92,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/
"app": "zabbix", "app": "zabbix",
"method": "api-token", "method": "api-token",
"credentials": { "credentials": {
"instanceUrl": "https://zabbix.example.com", "instanceUrl": "https://zabbix.example.com"
} }
} }
} }
+2 -2
View File
@@ -37,8 +37,8 @@ description: "Learn how to configure a Zabbix Sync for Infisical."
- **Zabbix Connection**: The Zabbix Connection to authenticate with. - **Zabbix Connection**: The Zabbix Connection to authenticate with.
- **Scope**: The Zabbix scope to sync secrets to. - **Scope**: The Zabbix scope to sync secrets to.
- **Global**: Secrets will be synced globally. - **Global**: Secrets will be synced globally.
- **Host**: Secrets will be synced to the specified host - **Host**: Secrets will be synced to the specified host.
- **Macro Type**: The type of macro to use when syncing secrets to Zabbix. - **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**: The remaining fields are determined by the selected **Scope**:
<AccordionGroup> <AccordionGroup>
<Accordion title="Host"> <Accordion title="Host">
@@ -5,6 +5,8 @@ import { GenericFieldLabel } from "@app/components/v2";
import { ZabbixSyncScope } from "@app/hooks/api/appConnections/zabbix"; import { ZabbixSyncScope } from "@app/hooks/api/appConnections/zabbix";
import { SecretSync } from "@app/hooks/api/secretSyncs"; import { SecretSync } from "@app/hooks/api/secretSyncs";
const isTextMacro = (macroType: number) => macroType === 0;
export const ZabbixSyncReviewFields = () => { export const ZabbixSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Zabbix }>(); const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Zabbix }>();
const scope = watch("destinationConfig.scope"); const scope = watch("destinationConfig.scope");
@@ -22,7 +24,7 @@ export const ZabbixSyncReviewFields = () => {
</> </>
)} )}
<GenericFieldLabel label="Macro Type"> <GenericFieldLabel label="Macro Type">
{macroType === 0 ? "Text" : "Secret"} {isTextMacro(macroType) ? "Text" : "Secret"}
</GenericFieldLabel> </GenericFieldLabel>
</> </>
); );
@@ -6,6 +6,8 @@ type Props = {
secretSync: TZabbixSync; secretSync: TZabbixSync;
}; };
const isTextMacro = (macroType: number) => macroType === 0;
export const ZabbixSyncDestinationSection = ({ secretSync }: Props) => { export const ZabbixSyncDestinationSection = ({ secretSync }: Props) => {
const { const {
destinationConfig: { macroType } destinationConfig: { macroType }
@@ -24,7 +26,7 @@ export const ZabbixSyncDestinationSection = ({ secretSync }: Props) => {
</> </>
)} )}
<GenericFieldLabel label="Macro Type"> <GenericFieldLabel label="Macro Type">
{macroType === 0 ? "Text" : "Secret"} {isTextMacro(macroType) ? "Text" : "Secret"}
</GenericFieldLabel> </GenericFieldLabel>
</> </>
); );