From 2bd9ad013753a4eb3859920e7bb7fa1f0cc00639 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 7 Jun 2024 21:46:34 +0800 Subject: [PATCH 1/5] feat: add option to maks and protect gitlab secrets --- backend/src/lib/api-docs/constants.ts | 4 +- .../server/routes/v1/integration-router.ts | 4 +- .../integration-sync-secret.ts | 10 +++-- .../services/integration/integration-types.ts | 2 + backend/src/services/secret/secret-queue.ts | 9 +++- .../src/hooks/api/integrations/queries.tsx | 2 + .../src/pages/integrations/gitlab/create.tsx | 45 +++++++++++++++++-- 7 files changed, 65 insertions(+), 11 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index da82016f1..deeaf5cd4 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -674,7 +674,9 @@ export const INTEGRATION = { secretGCPLabel: "The label for GCP secrets.", secretAWSTag: "The tags for AWS secrets.", kmsKeyId: "The ID of the encryption key from AWS KMS.", - shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store." + shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.", + shouldMaskSecrets: "The flag to determine the visibility of secrets to sync. Used by Gitlab", + shouldProtectSecrets: "The flag to determine usage rules of secrets to sync. Used by Gitlab" } }, UPDATE: { diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index bdb58aa8b..1c6bad7c7 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -73,7 +73,9 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { .optional() .describe(INTEGRATION.CREATE.metadata.secretAWSTag), kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId), - shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete) + shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete), + shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets), + shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets) }) .default({}) }), diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 9d8ee8895..15c80068d 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -1917,13 +1917,13 @@ const syncSecretsGitLab = async ({ return allEnvVariables; }; + const metadata = z.record(z.any()).parse(integration.metadata); const allEnvVariables = await getAllEnvVariables(integration?.appId as string, accessToken); const getSecretsRes: GitLabSecret[] = allEnvVariables .filter((secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment) .filter((gitLabSecret) => { let isValid = true; - const metadata = z.record(z.any()).parse(integration.metadata); if (metadata.secretPrefix && !gitLabSecret.key.startsWith(metadata.secretPrefix)) { isValid = false; } @@ -1943,8 +1943,8 @@ const syncSecretsGitLab = async ({ { key, value: secrets[key].value, - protected: false, - masked: false, + protected: Boolean(metadata.shouldProtectSecrets), + masked: Boolean(metadata.shouldMaskSecrets), raw: false, environment_scope: integration.targetEnvironment }, @@ -1961,7 +1961,9 @@ const syncSecretsGitLab = async ({ `${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`, { ...existingSecret, - value: secrets[existingSecret.key].value + value: secrets[existingSecret.key].value, + protected: Boolean(metadata.shouldProtectSecrets), + masked: Boolean(metadata.shouldMaskSecrets) }, { headers: { diff --git a/backend/src/services/integration/integration-types.ts b/backend/src/services/integration/integration-types.ts index 9c75cad2d..b96bb6ef3 100644 --- a/backend/src/services/integration/integration-types.ts +++ b/backend/src/services/integration/integration-types.ts @@ -29,6 +29,8 @@ export type TCreateIntegrationDTO = { }[]; kmsKeyId?: string; shouldDisableDelete?: boolean; + shouldMaskSecrets?: boolean; + shouldProtectSecrets?: boolean; }; } & Omit; diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index d40a18e5e..07db646f8 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -1,4 +1,6 @@ /* eslint-disable no-await-in-loop */ +import { AxiosError } from "axios"; + import { getConfig } from "@app/lib/config/env"; import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; @@ -567,11 +569,14 @@ export const secretQueueFactory = ({ isSynced: true }); } catch (err: unknown) { - logger.info("Secret integration sync error:", err); + logger.info("Secret integration sync error: %o", err); + const message = + err instanceof AxiosError ? JSON.stringify((err as AxiosError)?.response?.data) : (err as Error)?.message; + await integrationDAL.updateById(integration.id, { lastSyncJobId: job.id, lastUsed: new Date(), - syncMessage: (err as Error)?.message, + syncMessage: message, isSynced: false }); } diff --git a/frontend/src/hooks/api/integrations/queries.tsx b/frontend/src/hooks/api/integrations/queries.tsx index 3aa8f3ed1..2036f6718 100644 --- a/frontend/src/hooks/api/integrations/queries.tsx +++ b/frontend/src/hooks/api/integrations/queries.tsx @@ -73,6 +73,8 @@ export const useCreateIntegration = () => { }[]; kmsKeyId?: string; shouldDisableDelete?: boolean; + shouldMaskSecrets?: boolean; + shouldProtectSecrets?: boolean; }; }) => { const { diff --git a/frontend/src/pages/integrations/gitlab/create.tsx b/frontend/src/pages/integrations/gitlab/create.tsx index 529fd037e..8f8f27469 100644 --- a/frontend/src/pages/integrations/gitlab/create.tsx +++ b/frontend/src/pages/integrations/gitlab/create.tsx @@ -25,6 +25,7 @@ import { ModalContent, Select, SelectItem, + Switch, Tab, TabList, TabPanel, @@ -58,7 +59,9 @@ const schema = yup.object({ targetAppId: yup.string().required("GitLab project is required"), targetEnvironment: yup.string(), secretPrefix: yup.string(), - secretSuffix: yup.string() + secretSuffix: yup.string(), + shouldMaskSecrets: yup.boolean(), + shouldProtectSecrets: yup.boolean() }); type FormData = yup.InferType; @@ -138,7 +141,9 @@ export default function GitLabCreateIntegrationPage() { targetAppId, targetEnvironment, secretPrefix, - secretSuffix + secretSuffix, + shouldMaskSecrets, + shouldProtectSecrets }: FormData) => { try { setIsLoading(true); @@ -156,7 +161,9 @@ export default function GitLabCreateIntegrationPage() { secretPath, metadata: { secretPrefix, - secretSuffix + secretSuffix, + shouldMaskSecrets, + shouldProtectSecrets } }); @@ -390,6 +397,38 @@ export default function GitLabCreateIntegrationPage() { exit={{ opacity: 0, translateX: 30 }} className="pb-[14.25rem]" > +
+ ( + onChange(isChecked)} + isChecked={value} + > +
+ Mask variables (requires secret values to match regex) +
+
+ )} + /> +
+
+ ( + onChange(isChecked)} + isChecked={value} + > + Protect secrets (only use in protected branches and tags) + + )} + /> +
Date: Mon, 10 Jun 2024 22:45:17 +0800 Subject: [PATCH 2/5] misc: used metadata schema parsing --- .../src/services/integration-auth/integration-sync-secret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index f1d4d51ed..6351b4d82 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -1921,7 +1921,7 @@ const syncSecretsGitLab = async ({ return allEnvVariables; }; - const metadata = z.record(z.any()).parse(integration.metadata); + const metadata = IntegrationMetadataSchema.parse(integration.metadata); const allEnvVariables = await getAllEnvVariables(integration?.appId as string, accessToken); const getSecretsRes: GitLabSecret[] = allEnvVariables .filter((secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment) From aafddaa8568d4886289f6dfbbc6941ce1eabe7de Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Mon, 10 Jun 2024 23:08:39 +0800 Subject: [PATCH 3/5] misc: finalized option label --- frontend/src/pages/integrations/gitlab/create.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/pages/integrations/gitlab/create.tsx b/frontend/src/pages/integrations/gitlab/create.tsx index 8f8f27469..71b4a94b4 100644 --- a/frontend/src/pages/integrations/gitlab/create.tsx +++ b/frontend/src/pages/integrations/gitlab/create.tsx @@ -407,9 +407,7 @@ export default function GitLabCreateIntegrationPage() { onCheckedChange={(isChecked) => onChange(isChecked)} isChecked={value} > -
- Mask variables (requires secret values to match regex) -
+
Mask values in build logs
)} /> From 9138ab8ed70eed27dda0249d4fba102089259b35 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 10 Jun 2024 17:01:16 -0400 Subject: [PATCH 4/5] update flag describe --- backend/src/lib/api-docs/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index f4ffb87ba..121bb33df 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -675,8 +675,8 @@ export const INTEGRATION = { secretAWSTag: "The tags for AWS secrets.", kmsKeyId: "The ID of the encryption key from AWS KMS.", shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.", - shouldMaskSecrets: "The flag to determine the visibility of secrets to sync. Used by Gitlab", - shouldProtectSecrets: "The flag to determine usage rules of secrets to sync. Used by Gitlab", + shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.", + shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.", shouldEnableDelete: "The flag to enable deletion of secrets" } }, From 8329cbf299930847c0eaabcfc3b0bfd57ae5b62d Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 10 Jun 2024 17:09:20 -0400 Subject: [PATCH 5/5] update toggle lable --- frontend/src/pages/integrations/gitlab/create.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/integrations/gitlab/create.tsx b/frontend/src/pages/integrations/gitlab/create.tsx index 71b4a94b4..c1c94471c 100644 --- a/frontend/src/pages/integrations/gitlab/create.tsx +++ b/frontend/src/pages/integrations/gitlab/create.tsx @@ -407,7 +407,7 @@ export default function GitLabCreateIntegrationPage() { onCheckedChange={(isChecked) => onChange(isChecked)} isChecked={value} > -
Mask values in build logs
+
Mark Infisical secrets in Gitlab as 'Masked' secrets
)} /> @@ -422,7 +422,7 @@ export default function GitLabCreateIntegrationPage() { onCheckedChange={(isChecked) => onChange(isChecked)} isChecked={value} > - Protect secrets (only use in protected branches and tags) + Mark Infisical secrets in Gitlab as 'Protected' secrets )} />