From 0366506213df7972c415466ad790bbcdc273dd40 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 10 Dec 2024 03:30:19 +0400 Subject: [PATCH] feat(azure-app-integration): label & reference support --- backend/src/lib/api-docs/constants.ts | 2 + .../integration-sync-secret.ts | 22 +++- .../integration/integration-schema.ts | 2 + .../src/hooks/api/integrations/queries.tsx | 1 + .../azure-app-configuration/create.tsx | 114 +++++++++++++----- 5 files changed, 109 insertions(+), 32 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index fabcae408..70e3d0ccd 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1126,6 +1126,8 @@ export const INTEGRATION = { shouldAutoRedeploy: "Used by Render to trigger auto deploy.", secretGCPLabel: "The label for GCP secrets.", secretAWSTag: "The tags for AWS secrets.", + azureUseLabels: + "If enabled, each secret will be given a label that represents which Infisical environment they belong to.", githubVisibility: "Define where the secrets from the Github Integration should be visible. Option 'selected' lets you directly define which repositories to sync secrets to.", githubVisibilityRepoIds: diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index c147150f0..7a1b104ce 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -299,6 +299,11 @@ const syncSecretsAzureAppConfig = async ({ value: string; } + // Format: {\"uri\":\"https://SOME-KEY-VAULT.vault.azure.net/secrets/SOME-SECRET-KEY\"} + // Also works without the backslash escapes + const azureSecretReferenceUriRegex = + /^\{(\\"|")uri(\\"|"):(\\"|")https:\/\/[a-zA-Z0-9-]+\.vault\.azure\.net\/secrets\/[a-zA-Z0-9-]+\\?\2\}$/; + const getCompleteAzureAppConfigValues = async (url: string) => { let result: AzureAppConfigKeyValue[] = []; while (url) { @@ -405,14 +410,24 @@ const syncSecretsAzureAppConfig = async ({ } // create or update secrets on Azure App Config + for await (const key of Object.keys(secrets)) { if (!(key in azureAppConfigSecrets) || secrets[key]?.value !== azureAppConfigSecrets[key]) { await request.put( `${integration.app}/kv/${key}?api-version=2023-11-01`, { - value: secrets[key]?.value + value: secrets[key]?.value, + ...(azureSecretReferenceUriRegex.test(secrets[key]?.value || "") && { + content_type: "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8" + }) }, { + ...(metadata.azureUseLabels && { + params: { + label: integration.environment.slug + } + }), + headers: { Authorization: `Bearer ${accessToken}` }, @@ -432,6 +447,11 @@ const syncSecretsAzureAppConfig = async ({ headers: { Authorization: `Bearer ${accessToken}` }, + ...(metadata.azureUseLabels && { + params: { + label: integration.environment.slug + } + }), // we force IPV4 because docker setup fails with ipv6 httpsAgent: new https.Agent({ family: 4 diff --git a/backend/src/services/integration/integration-schema.ts b/backend/src/services/integration/integration-schema.ts index d047a0c11..01a928db0 100644 --- a/backend/src/services/integration/integration-schema.ts +++ b/backend/src/services/integration/integration-schema.ts @@ -35,6 +35,8 @@ export const IntegrationMetadataSchema = z.object({ .optional() .describe(INTEGRATION.CREATE.metadata.secretAWSTag), + azureUseLabels: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.azureUseLabels), + githubVisibility: z .union([z.literal("selected"), z.literal("private"), z.literal("all")]) .optional() diff --git a/frontend/src/hooks/api/integrations/queries.tsx b/frontend/src/hooks/api/integrations/queries.tsx index 11d42631a..a40ef35eb 100644 --- a/frontend/src/hooks/api/integrations/queries.tsx +++ b/frontend/src/hooks/api/integrations/queries.tsx @@ -80,6 +80,7 @@ export const useCreateIntegration = () => { key: string; value: string; }[]; + azureUseLabels?: boolean; githubVisibility?: string; githubVisibilityRepoIds?: string[]; kmsKeyId?: string; diff --git a/frontend/src/pages/integrations/azure-app-configuration/create.tsx b/frontend/src/pages/integrations/azure-app-configuration/create.tsx index c9fe4d1db..a7b137adf 100644 --- a/frontend/src/pages/integrations/azure-app-configuration/create.tsx +++ b/frontend/src/pages/integrations/azure-app-configuration/create.tsx @@ -4,7 +4,11 @@ import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { + faArrowUpRightFromSquare, + faBookOpen, + faQuestionCircle +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import queryString from "query-string"; @@ -21,7 +25,9 @@ import { FormControl, Input, Select, - SelectItem + SelectItem, + Switch, + Tooltip } from "../../../components/v2"; import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth"; import { useGetWorkspaceById } from "../../../hooks/api/workspace"; @@ -39,7 +45,8 @@ const schema = z.object({ secretPath: z.string().trim().min(1, { message: "Secret path is required" }), sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }), initialSyncBehavior: z.nativeEnum(IntegrationSyncBehavior), - secretPrefix: z.string().default("") + secretPrefix: z.string().default(""), + useLabels: z.boolean().default(false) }); type TFormSchema = z.infer; @@ -60,6 +67,7 @@ export default function AzureAppConfigurationCreateIntegration() { const router = useRouter(); const { control, + watch, setValue, handleSubmit, formState: { isSubmitting } @@ -85,8 +93,11 @@ export default function AzureAppConfigurationCreateIntegration() { } }, [workspace]); + const sourceEnv = watch("sourceEnvironment"); + const handleIntegrationSubmit = async ({ secretPath, + useLabels, sourceEnvironment, baseUrl, initialSyncBehavior, @@ -103,7 +114,8 @@ export default function AzureAppConfigurationCreateIntegration() { secretPath, metadata: { initialSyncBehavior, - secretPrefix + secretPrefix, + azureUseLabels: useLabels } }); @@ -155,35 +167,75 @@ export default function AzureAppConfigurationCreateIntegration() {
- ( - - + + )} + /> + + ( + onChange(isChecked)} + isChecked={value} + > +
+ Use Environment Labels + +

+ Use the environment slug as the label on the secret keys created in + Azure App Configuration. +
+
+ {sourceEnv && ( +

+ You have selected the{" "} + {sourceEnv} environment, + therefore the label will be set to{" "} + {sourceEnv}. +

+ )} +

+
+ } > - {sourceEnvironment.name} - - ))} - - - )} - /> + + +
+ + )} + /> +