From a6921485976a17c97080050eacb636cb7d35b491 Mon Sep 17 00:00:00 2001 From: McPizza Date: Sat, 23 Nov 2024 00:04:33 +0100 Subject: [PATCH] feat(integrations): Add AWS Secrets Manager IAM Role + Region (#2778) --- .../server/routes/v1/integration-router.ts | 28 +++++++++++++ .../integration/integration-service.ts | 42 +++++++++++++++++++ frontend/src/hooks/api/integrations/types.ts | 3 ++ .../components/IntegrationSettingsSection.tsx | 4 +- 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index 86d321852..40141e2c0 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -9,6 +9,7 @@ import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema"; +import { Integrations } from "@app/services/integration-auth/integration-list"; import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types"; import {} from "../sanitizedSchemas"; @@ -206,6 +207,33 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { id: req.params.integrationId }); + if (integration.region) { + integration.metadata = { + ...(integration.metadata || {}), + region: integration.region + }; + } + + if ( + integration.integration === Integrations.AWS_SECRET_MANAGER || + integration.integration === Integrations.AWS_PARAMETER_STORE + ) { + const awsRoleDetails = await server.services.integration.getIntegrationAWSIamRole({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.integrationId + }); + + if (awsRoleDetails) { + integration.metadata = { + ...(integration.metadata || {}), + awsIamRole: awsRoleDetails.role + }; + } + } + return { integration }; } }); diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index 12f4c77de..1db10405d 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -9,6 +9,7 @@ import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-auth-service"; import { deleteIntegrationSecrets } from "../integration-auth/integration-delete-secret"; import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { TSecretDALFactory } from "../secret/secret-dal"; import { TSecretQueueFactory } from "../secret/secret-queue"; @@ -237,6 +238,46 @@ export const integrationServiceFactory = ({ return { ...integration, envId: integration.environment.id }; }; + const getIntegrationAWSIamRole = async ({ id, actor, actorAuthMethod, actorId, actorOrgId }: TGetIntegrationDTO) => { + const integration = await integrationDAL.findById(id); + + if (!integration) { + throw new NotFoundError({ + message: `Integration with ID '${id}' not found` + }); + } + + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integration?.projectId || "", + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); + + const integrationAuth = await integrationAuthDAL.findById(integration.integrationAuthId); + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: integration.projectId + }); + let awsIamRole: string | null = null; + if (integrationAuth.encryptedAwsAssumeIamRoleArn) { + const awsAssumeRoleArn = secretManagerDecryptor({ + cipherTextBlob: Buffer.from(integrationAuth.encryptedAwsAssumeIamRoleArn) + }).toString(); + if (awsAssumeRoleArn) { + const [, role] = awsAssumeRoleArn.split(":role/"); + awsIamRole = role; + } + } + + return { + role: awsIamRole + }; + }; + const deleteIntegration = async ({ actorId, id, @@ -329,6 +370,7 @@ export const integrationServiceFactory = ({ deleteIntegration, listIntegrationByProject, getIntegration, + getIntegrationAWSIamRole, syncIntegration }; }; diff --git a/frontend/src/hooks/api/integrations/types.ts b/frontend/src/hooks/api/integrations/types.ts index bfaa73884..17e7265d6 100644 --- a/frontend/src/hooks/api/integrations/types.ts +++ b/frontend/src/hooks/api/integrations/types.ts @@ -57,6 +57,9 @@ export type TIntegration = { shouldMaskSecrets?: boolean; shouldProtectSecrets?: boolean; shouldEnableDelete?: boolean; + + awsIamRole?: string; + region?: string; }; }; diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx index 653204455..ee30e3968 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx @@ -26,7 +26,9 @@ const metadataMappings: Record {