misc: revisions to import secret flow

This commit is contained in:
Sheen Capadngan
2025-10-15 01:28:30 +08:00
parent 43498573c5
commit d3b4ef6088
7 changed files with 74 additions and 50 deletions
@@ -7,6 +7,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { import {
ExternalMigrationProviders, ExternalMigrationProviders,
VaultImportStatus,
VaultMappingType VaultMappingType
} from "@app/services/external-migration/external-migration-types"; } from "@app/services/external-migration/external-migration-types";
@@ -277,19 +278,19 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
}), }),
response: { response: {
200: z.object({ 200: z.object({
message: z.string() status: z.nativeEnum(VaultImportStatus)
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
await server.services.migration.importVaultSecrets({ const result = await server.services.migration.importVaultSecrets({
actor: req.permission, actor: req.permission,
auditLogInfo: req.auditLogInfo, auditLogInfo: req.auditLogInfo,
...req.body ...req.body
}); });
return { message: "Successfully imported vault secrets" }; return result;
} }
}); });
@@ -43,7 +43,8 @@ import {
TConfigureExternalMigrationDTO, TConfigureExternalMigrationDTO,
THasCustomVaultMigrationDTO, THasCustomVaultMigrationDTO,
TImportEnvKeyDataDTO, TImportEnvKeyDataDTO,
TImportVaultDataDTO TImportVaultDataDTO,
VaultImportStatus
} from "./external-migration-types"; } from "./external-migration-types";
type TExternalMigrationServiceFactoryDep = { type TExternalMigrationServiceFactoryDep = {
@@ -487,41 +488,49 @@ export const externalMigrationServiceFactory = ({
const vaultSecrets = await getHCVaultSecretsForPath(vaultNamespace, vaultSecretPath, connection, gatewayService); const vaultSecrets = await getHCVaultSecretsForPath(vaultNamespace, vaultSecretPath, connection, gatewayService);
const secretOperation = await secretService.createManySecretsRaw({ try {
actorId: actor.id, const secretOperation = await secretService.createManySecretsRaw({
actor: actor.type, actorId: actor.id,
actorAuthMethod: actor.authMethod, actor: actor.type,
actorOrgId: actor.orgId, actorAuthMethod: actor.authMethod,
secretPath, actorOrgId: actor.orgId,
environment, secretPath,
projectId, environment,
secrets: Object.entries(vaultSecrets).map(([secretKey, secretValue]) => ({
secretKey,
secretValue
}))
});
if (secretOperation.type === SecretProtectionType.Approval) {
await auditLogService.createAuditLog({
projectId, projectId,
...auditLogInfo, secrets: Object.entries(vaultSecrets).map(([secretKey, secretValue]) => ({
event: { secretKey,
type: EventType.SECRET_APPROVAL_REQUEST, secretValue
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 { 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"}`
});
} }
}; };
@@ -122,6 +122,11 @@ export enum ExternalMigrationProviders {
EnvKey = "env-key" EnvKey = "env-key"
} }
export enum VaultImportStatus {
Imported = "imported",
ApprovalRequired = "approval_required"
}
export type TConfigureExternalMigrationDTO = { export type TConfigureExternalMigrationDTO = {
platform: ExternalMigrationProviders; platform: ExternalMigrationProviders;
connectionId: string | null; connectionId: string | null;
@@ -9,7 +9,8 @@ import { externalMigrationQueryKeys } from "./queries";
import { import {
ExternalMigrationProviders, ExternalMigrationProviders,
TExternalMigrationConfig, TExternalMigrationConfig,
TImportVaultSecretsDTO TImportVaultSecretsDTO,
VaultImportStatus
} from "./types"; } from "./types";
export const useImportEnvKey = () => { export const useImportEnvKey = () => {
@@ -100,9 +101,9 @@ export const useUpdateExternalMigrationConfig = (platform: ExternalMigrationProv
export const useImportVaultSecrets = () => { export const useImportVaultSecrets = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<{ message: string }, object, TImportVaultSecretsDTO>({ return useMutation<{ status: VaultImportStatus }, object, TImportVaultSecretsDTO>({
mutationFn: async (dto) => { mutationFn: async (dto) => {
const { data } = await apiRequest.post<{ message: string }>( const { data } = await apiRequest.post<{ status: VaultImportStatus }>(
"/api/v3/external-migration/vault/import-secrets", "/api/v3/external-migration/vault/import-secrets",
dto dto
); );
@@ -3,6 +3,11 @@ export enum ExternalMigrationProviders {
EnvKey = "env-key" EnvKey = "env-key"
} }
export enum VaultImportStatus {
Imported = "imported",
ApprovalRequired = "approval_required"
}
export type TExternalMigrationConfig = { export type TExternalMigrationConfig = {
id: string; id: string;
orgId: string; orgId: string;
@@ -78,7 +78,7 @@ import {
} from "@app/hooks/api/dashboard/queries"; } from "@app/hooks/api/dashboard/queries";
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types"; import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
import { useGetExternalMigrationConfig, useImportVaultSecrets } from "@app/hooks/api/migration"; 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 { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { PendingAction } from "@app/hooks/api/secretFolders/types"; import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries"; import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries";
@@ -672,7 +672,7 @@ export const ActionBar = ({
const handleVaultImport = async (vaultPath: string, namespace: string) => { const handleVaultImport = async (vaultPath: string, namespace: string) => {
try { try {
await importVaultSecrets({ const result = await importVaultSecrets({
projectId, projectId,
environment, environment,
secretPath, secretPath,
@@ -680,10 +680,17 @@ export const ActionBar = ({
vaultSecretPath: vaultPath vaultSecretPath: vaultPath
}); });
createNotification({ if (result.status === VaultImportStatus.ApprovalRequired) {
type: "success", createNotification({
text: "Successfully imported secrets from HashiCorp Vault" type: "info",
}); text: "Secret change request created successfully. Awaiting approval."
});
} else {
createNotification({
type: "success",
text: "Successfully imported secrets from HashiCorp Vault"
});
}
} catch (err) { } catch (err) {
console.error("Vault import error:", err); console.error("Vault import error:", err);
const error = err as AxiosError<{ message?: string }>; const error = err as AxiosError<{ message?: string }>;
@@ -88,13 +88,9 @@ const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) =
<div className="space-y-1.5 text-xs leading-relaxed"> <div className="space-y-1.5 text-xs leading-relaxed">
<p> <p>
Select a Vault namespace and secret path to import secrets into the current Select a Vault namespace and secret path to import secrets into the current
environment (<code className="text-xs">{environment}</code>) at path{" "} Infisical environment (<code className="text-xs">{environment}</code>) at path{" "}
<code className="text-xs">{secretPath}</code>. <code className="text-xs">{secretPath}</code>.
</p> </p>
<p>
<strong>Note:</strong> Existing secrets with the same key will be overwritten.
Secrets will be imported from the selected Vault path.
</p>
</div> </div>
</div> </div>
</div> </div>