diff --git a/backend/src/server/plugins/maintenanceMode.ts b/backend/src/server/plugins/maintenanceMode.ts index f40f1ff6d..201dd05b8 100644 --- a/backend/src/server/plugins/maintenanceMode.ts +++ b/backend/src/server/plugins/maintenanceMode.ts @@ -5,8 +5,13 @@ import { getConfig } from "@app/lib/config/env"; export const maintenanceMode = fp(async (fastify) => { fastify.addHook("onRequest", async (req) => { const serverEnvs = getConfig(); - if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET" && serverEnvs.MAINTENANCE_MODE) { - throw new Error("Infisical is in maintenance mode. Please try again later."); + if (serverEnvs.MAINTENANCE_MODE) { + // skip if its universal auth login or renew + if (req.url === "/api/v1/auth/universal-auth/login" && req.method === "POST") return; + if (req.url === "/api/v1/auth/token/renew" && req.method === "POST") return; + if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET") { + throw new Error("Infisical is in maintenance mode. Please try again later."); + } } }); }); diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index 975e6e7c8..e40e3f799 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -143,8 +143,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId) }), body: z.object({ - app: z.string().trim().describe(INTEGRATION.UPDATE.app), - appId: z.string().trim().describe(INTEGRATION.UPDATE.appId), + app: z.string().trim().optional().describe(INTEGRATION.UPDATE.app), + appId: z.string().trim().optional().describe(INTEGRATION.UPDATE.appId), isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive), secretPath: z .string() @@ -154,7 +154,33 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { .describe(INTEGRATION.UPDATE.secretPath), targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment), owner: z.string().trim().describe(INTEGRATION.UPDATE.owner), - environment: z.string().trim().describe(INTEGRATION.UPDATE.environment) + environment: z.string().trim().describe(INTEGRATION.UPDATE.environment), + metadata: z + .object({ + secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix), + secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix), + initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir), + shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy), + secretGCPLabel: z + .object({ + labelName: z.string(), + labelValue: z.string() + }) + .optional() + .describe(INTEGRATION.CREATE.metadata.secretGCPLabel), + secretAWSTag: z + .array( + z.object({ + key: z.string(), + value: z.string() + }) + ) + .optional() + .describe(INTEGRATION.CREATE.metadata.secretAWSTag), + kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId), + shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete) + }) + .optional() }), response: { 200: z.object({ diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 4e1dbd7ae..963db1e1e 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -9,9 +9,12 @@ import { CreateSecretCommand, + DescribeSecretCommand, GetSecretValueCommand, ResourceNotFoundException, SecretsManagerClient, + TagResourceCommand, + UntagResourceCommand, UpdateSecretCommand } from "@aws-sdk/client-secrets-manager"; import { Octokit } from "@octokit/rest"; @@ -574,6 +577,7 @@ const syncSecretsAWSSecretManager = async ({ if (awsSecretManagerSecret?.SecretString) { awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString); } + if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) { await secretsManager.send( new UpdateSecretCommand({ @@ -582,7 +586,88 @@ const syncSecretsAWSSecretManager = async ({ }) ); } + + const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined; + + if (secretAWSTag && secretAWSTag.length) { + const describedSecret = await secretsManager.send( + // requires secretsmanager:DescribeSecret policy + new DescribeSecretCommand({ + SecretId: integration.app as string + }) + ); + + if (!describedSecret.Tags) return; + + const integrationTagObj = secretAWSTag.reduce( + (acc, item) => { + acc[item.key] = item.value; + return acc; + }, + {} as Record + ); + + const awsTagObj = (describedSecret.Tags || []).reduce( + (acc, item) => { + if (item.Key && item.Value) { + acc[item.Key] = item.Value; + } + return acc; + }, + {} as Record + ); + + const tagsToUpdate: { Key: string; Value: string }[] = []; + const tagsToDelete: { Key: string; Value: string }[] = []; + + describedSecret.Tags?.forEach((tag) => { + if (tag.Key && tag.Value) { + if (!(tag.Key in integrationTagObj)) { + // delete tag from AWS secret manager + tagsToDelete.push({ + Key: tag.Key, + Value: tag.Value + }); + } else if (tag.Value !== integrationTagObj[tag.Key]) { + // update tag in AWS secret manager + tagsToUpdate.push({ + Key: tag.Key, + Value: integrationTagObj[tag.Key] + }); + } + } + }); + + secretAWSTag?.forEach((tag) => { + if (!(tag.key in awsTagObj)) { + // create tag in AWS secret manager + tagsToUpdate.push({ + Key: tag.key, + Value: tag.value + }); + } + }); + + if (tagsToUpdate.length) { + await secretsManager.send( + new TagResourceCommand({ + SecretId: integration.app as string, + Tags: tagsToUpdate + }) + ); + } + + if (tagsToDelete.length) { + await secretsManager.send( + new UntagResourceCommand({ + SecretId: integration.app as string, + TagKeys: tagsToDelete.map((tag) => tag.Key) + }) + ); + } + } } catch (err) { + // case when AWS manager can't find the specified secret if (err instanceof ResourceNotFoundException && secretsManager) { await secretsManager.send( new CreateSecretCommand({ diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index a3c4c84db..f4f8cdb44 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -103,7 +103,8 @@ export const integrationServiceFactory = ({ owner, isActive, environment, - secretPath + secretPath, + metadata }: TUpdateIntegrationDTO) => { const integration = await integrationDAL.findById(id); if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); @@ -127,7 +128,17 @@ export const integrationServiceFactory = ({ appId, targetEnvironment, owner, - secretPath + secretPath, + metadata: { + ...(integration.metadata as object), + ...metadata + } + }); + + await secretQueueService.syncIntegrations({ + environment: folder.environment.slug, + secretPath, + projectId: folder.projectId }); return updatedIntegration; diff --git a/backend/src/services/integration/integration-types.ts b/backend/src/services/integration/integration-types.ts index 1913dd31f..50d0de762 100644 --- a/backend/src/services/integration/integration-types.ts +++ b/backend/src/services/integration/integration-types.ts @@ -33,13 +33,27 @@ export type TCreateIntegrationDTO = { export type TUpdateIntegrationDTO = { id: string; - app: string; - appId: string; + app?: string; + appId?: string; isActive?: boolean; secretPath: string; targetEnvironment: string; owner: string; environment: string; + metadata?: { + secretPrefix?: string; + secretSuffix?: string; + secretGCPLabel?: { + labelName: string; + labelValue: string; + }; + secretAWSTag?: { + key: string; + value: string; + }[]; + kmsKeyId?: string; + shouldDisableDelete?: boolean; + }; } & Omit; export type TDeleteIntegrationDTO = { diff --git a/docs/integrations/cloud/aws-secret-manager.mdx b/docs/integrations/cloud/aws-secret-manager.mdx index 6fc83eaff..db95c8308 100644 --- a/docs/integrations/cloud/aws-secret-manager.mdx +++ b/docs/integrations/cloud/aws-secret-manager.mdx @@ -29,7 +29,9 @@ Prerequisites: "secretsmanager:GetSecretValue", "secretsmanager:CreateSecret", "secretsmanager:UpdateSecret", + "secretsmanager:DescribeSecret", // if you need to add tags to secrets "secretsmanager:TagResource", // if you need to add tags to secrets + "secretsmanager:UntagResource", // if you need to add tags to secrets "kms:ListKeys", // if you need to specify the KMS key "kms:ListAliases", // if you need to specify the KMS key "kms:Encrypt", // if you need to specify the KMS key diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index ab9fa790d..17cf8537c 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -21,9 +21,7 @@ import { faNetworkWired, faPlug, faPlus, - faUserPlus, - faWarning, - faXmark + faUserPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -56,7 +54,6 @@ import { fetchOrgUsers, useAddUserToWsNonE2EE, useCreateWorkspace, - useGetUserAction, useRegisterUserAction } from "@app/hooks/api"; // import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; @@ -312,9 +309,8 @@ const LearningItem = ({ href={link} >
null} @@ -325,11 +321,10 @@ const LearningItem = ({ await registerUserAction.mutateAsync(userAction); } }} - className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${ - complete + className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${complete ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" - } text-mineshaft-100 duration-200`} + } text-mineshaft-100 duration-200`} >
@@ -407,9 +402,8 @@ const LearningItemSquare = ({ href={link} >
null} @@ -420,11 +414,10 @@ const LearningItemSquare = ({ await registerUserAction.mutateAsync(userAction); } }} - className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${ - complete + className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${complete ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" - } text-mineshaft-100 duration-200`} + } text-mineshaft-100 duration-200`} >
@@ -438,9 +431,8 @@ const LearningItemSquare = ({
)}
{complete ? "Complete!" : `About ${time}`}
@@ -480,14 +472,8 @@ const OrganizationPage = withPermission( const { currentOrg } = useOrganization(); const routerOrgId = String(router.query.id); const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === routerOrgId) || []; - - const addUsersToProject = useAddUserToWsNonE2EE(); - const { data: updateClosed } = useGetUserAction("april_13_2024_db_update_closed"); - const registerUserAction = useRegisterUserAction(); - const closeUpdate = async () => { - await registerUserAction.mutateAsync("april_13_2024_db_update_closed"); - }; + const addUsersToProject = useAddUserToWsNonE2EE(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addNewWs", @@ -594,31 +580,6 @@ const OrganizationPage = withPermission(
)}
- {(window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("http://localhost:8080")) && ( -
- -
- Scheduled maintenance on May 11th 2024 {" "} -
- Infisical will undergo scheduled maintenance for approximately 2 hour on Saturday, May 11th, 11am EST. During these hours, read - operations to Infisical will continue to function normally but no resources will be editable. - No action is required on your end — your applications will continue to fetch secrets. -
-
- -
)} -

Projects

-

Onboarding Guide

-
- - {orgWorkspaces.length !== 0 && ( - <> - - - - )} -
+
+

Onboarding Guide

+
+ {orgWorkspaces.length !== 0 && ( + <> + + + + )} +
+ +
-
- {orgWorkspaces.length !== 0 && ( -
-
-
- - {false && ( -
- -
- )} -
-
Inject secrets locally
-
- Replace .env files with a more secure and efficient alternative. + {orgWorkspaces.length !== 0 && ( +
+
+
+ + {false && ( +
+ +
+ )} +
+
Inject secrets locally
+
+ Replace .env files with a more secure and efficient alternative. +
+
+ About 2 min +
-
- About 2 min -
+ + {false &&
}
- - {false &&
} -
- )} - {orgWorkspaces.length !== 0 && ( - - )} -
- )} + )} + {orgWorkspaces.length !== 0 && ( + + )} +
+ )} {