diff --git a/backend/src/server/routes/v3/external-migration-router.ts b/backend/src/server/routes/v3/external-migration-router.ts index 8ab5acff2..83b7a8275 100644 --- a/backend/src/server/routes/v3/external-migration-router.ts +++ b/backend/src/server/routes/v3/external-migration-router.ts @@ -7,6 +7,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { ExternalMigrationProviders, + VaultImportStatus, VaultMappingType } from "@app/services/external-migration/external-migration-types"; @@ -277,19 +278,19 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider }), response: { 200: z.object({ - message: z.string() + status: z.nativeEnum(VaultImportStatus) }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - await server.services.migration.importVaultSecrets({ + const result = await server.services.migration.importVaultSecrets({ actor: req.permission, auditLogInfo: req.auditLogInfo, ...req.body }); - return { message: "Successfully imported vault secrets" }; + return result; } }); diff --git a/backend/src/services/external-migration/external-migration-service.ts b/backend/src/services/external-migration/external-migration-service.ts index a1bc57c60..d1489a347 100644 --- a/backend/src/services/external-migration/external-migration-service.ts +++ b/backend/src/services/external-migration/external-migration-service.ts @@ -43,7 +43,8 @@ import { TConfigureExternalMigrationDTO, THasCustomVaultMigrationDTO, TImportEnvKeyDataDTO, - TImportVaultDataDTO + TImportVaultDataDTO, + VaultImportStatus } from "./external-migration-types"; type TExternalMigrationServiceFactoryDep = { @@ -487,41 +488,49 @@ export const externalMigrationServiceFactory = ({ const vaultSecrets = await getHCVaultSecretsForPath(vaultNamespace, vaultSecretPath, connection, gatewayService); - const secretOperation = await secretService.createManySecretsRaw({ - actorId: actor.id, - actor: actor.type, - actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - secretPath, - environment, - projectId, - secrets: Object.entries(vaultSecrets).map(([secretKey, secretValue]) => ({ - secretKey, - secretValue - })) - }); - - if (secretOperation.type === SecretProtectionType.Approval) { - await auditLogService.createAuditLog({ + try { + const secretOperation = await secretService.createManySecretsRaw({ + actorId: actor.id, + actor: actor.type, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + secretPath, + environment, projectId, - ...auditLogInfo, - event: { - type: EventType.SECRET_APPROVAL_REQUEST, - metadata: { - committedBy: secretOperation.approval.committerUserId, - secretApprovalRequestId: secretOperation.approval.id, - secretApprovalRequestSlug: secretOperation.approval.slug, - secretPath, - environment, - secrets: Object.entries(vaultSecrets).map(([secretKey]) => ({ - secretKey - })), - eventType: SecretApprovalEvent.CreateMany - } - } + secrets: Object.entries(vaultSecrets).map(([secretKey, secretValue]) => ({ + secretKey, + secretValue + })) }); - return { approval: secretOperation.approval }; + if (secretOperation.type === SecretProtectionType.Approval) { + await auditLogService.createAuditLog({ + projectId, + ...auditLogInfo, + event: { + type: EventType.SECRET_APPROVAL_REQUEST, + metadata: { + committedBy: secretOperation.approval.committerUserId, + secretApprovalRequestId: secretOperation.approval.id, + secretApprovalRequestSlug: secretOperation.approval.slug, + secretPath, + environment, + secrets: Object.entries(vaultSecrets).map(([secretKey]) => ({ + secretKey + })), + eventType: SecretApprovalEvent.CreateMany + } + } + }); + + return { status: VaultImportStatus.ApprovalRequired }; + } + + return { status: VaultImportStatus.Imported }; + } catch (error) { + throw new BadRequestError({ + message: `Failed to import Vault secrets. ${error instanceof Error ? error.message : "Unknown error"}` + }); } }; diff --git a/backend/src/services/external-migration/external-migration-types.ts b/backend/src/services/external-migration/external-migration-types.ts index 565db6e71..da2d04771 100644 --- a/backend/src/services/external-migration/external-migration-types.ts +++ b/backend/src/services/external-migration/external-migration-types.ts @@ -122,6 +122,11 @@ export enum ExternalMigrationProviders { EnvKey = "env-key" } +export enum VaultImportStatus { + Imported = "imported", + ApprovalRequired = "approval_required" +} + export type TConfigureExternalMigrationDTO = { platform: ExternalMigrationProviders; connectionId: string | null; diff --git a/frontend/src/hooks/api/migration/mutations.tsx b/frontend/src/hooks/api/migration/mutations.tsx index c3c55ad5a..53c6c2fb4 100644 --- a/frontend/src/hooks/api/migration/mutations.tsx +++ b/frontend/src/hooks/api/migration/mutations.tsx @@ -9,7 +9,8 @@ import { externalMigrationQueryKeys } from "./queries"; import { ExternalMigrationProviders, TExternalMigrationConfig, - TImportVaultSecretsDTO + TImportVaultSecretsDTO, + VaultImportStatus } from "./types"; export const useImportEnvKey = () => { @@ -100,9 +101,9 @@ export const useUpdateExternalMigrationConfig = (platform: ExternalMigrationProv export const useImportVaultSecrets = () => { const queryClient = useQueryClient(); - return useMutation<{ message: string }, object, TImportVaultSecretsDTO>({ + return useMutation<{ status: VaultImportStatus }, object, TImportVaultSecretsDTO>({ mutationFn: async (dto) => { - const { data } = await apiRequest.post<{ message: string }>( + const { data } = await apiRequest.post<{ status: VaultImportStatus }>( "/api/v3/external-migration/vault/import-secrets", dto ); diff --git a/frontend/src/hooks/api/migration/types.ts b/frontend/src/hooks/api/migration/types.ts index 217d6fdff..71fdecfbd 100644 --- a/frontend/src/hooks/api/migration/types.ts +++ b/frontend/src/hooks/api/migration/types.ts @@ -3,6 +3,11 @@ export enum ExternalMigrationProviders { EnvKey = "env-key" } +export enum VaultImportStatus { + Imported = "imported", + ApprovalRequired = "approval_required" +} + export type TExternalMigrationConfig = { id: string; orgId: string; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 41c0af267..68172b2d5 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -78,7 +78,7 @@ import { } from "@app/hooks/api/dashboard/queries"; import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types"; import { useGetExternalMigrationConfig, useImportVaultSecrets } from "@app/hooks/api/migration"; -import { ExternalMigrationProviders } from "@app/hooks/api/migration/types"; +import { ExternalMigrationProviders, VaultImportStatus } from "@app/hooks/api/migration/types"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { PendingAction } from "@app/hooks/api/secretFolders/types"; import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries"; @@ -672,7 +672,7 @@ export const ActionBar = ({ const handleVaultImport = async (vaultPath: string, namespace: string) => { try { - await importVaultSecrets({ + const result = await importVaultSecrets({ projectId, environment, secretPath, @@ -680,10 +680,17 @@ export const ActionBar = ({ vaultSecretPath: vaultPath }); - createNotification({ - type: "success", - text: "Successfully imported secrets from HashiCorp Vault" - }); + if (result.status === VaultImportStatus.ApprovalRequired) { + createNotification({ + type: "info", + text: "Secret change request created successfully. Awaiting approval." + }); + } else { + createNotification({ + type: "success", + text: "Successfully imported secrets from HashiCorp Vault" + }); + } } catch (err) { console.error("Vault import error:", err); const error = err as AxiosError<{ message?: string }>; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/VaultSecretImportModal.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/VaultSecretImportModal.tsx index fd689baf3..b691c5418 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/VaultSecretImportModal.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/VaultSecretImportModal.tsx @@ -88,13 +88,9 @@ const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) =

Select a Vault namespace and secret path to import secrets into the current - environment ({environment}) at path{" "} + Infisical environment ({environment}) at path{" "} {secretPath}.

-

- Note: Existing secrets with the same key will be overwritten. - Secrets will be imported from the selected Vault path. -