mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 03:26:12 +00:00
Merge pull request #2573 from Infisical/daniel/envkey-import-bug
feat: Process Envkey import in queue
This commit is contained in:
@@ -240,7 +240,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const snapshotSecrets = await snapshotSecretV2BridgeDAL.insertMany(
|
|
||||||
|
const snapshotSecrets = await snapshotSecretV2BridgeDAL.batchInsert(
|
||||||
secretVersions.map(({ id }) => ({
|
secretVersions.map(({ id }) => ({
|
||||||
secretVersionId: id,
|
secretVersionId: id,
|
||||||
envId: folder.environment.envId,
|
envId: folder.environment.envId,
|
||||||
@@ -248,7 +249,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const snapshotFolders = await snapshotFolderDAL.insertMany(
|
|
||||||
|
const snapshotFolders = await snapshotFolderDAL.batchInsert(
|
||||||
folderVersions.map(({ id }) => ({
|
folderVersions.map(({ id }) => ({
|
||||||
folderVersionId: id,
|
folderVersionId: id,
|
||||||
envId: folder.environment.envId,
|
envId: folder.environment.envId,
|
||||||
|
|||||||
@@ -70,3 +70,14 @@ export const objectify = <T, Key extends string | number | symbol, Value = T>(
|
|||||||
{} as Record<Key, Value>
|
{} as Record<Key, Value>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunks an array into smaller arrays of the given size.
|
||||||
|
*/
|
||||||
|
export const chunkArray = <T>(array: T[], chunkSize: number): T[][] => {
|
||||||
|
const chunks: T[][] = [];
|
||||||
|
for (let i = 0; i < array.length; i += chunkSize) {
|
||||||
|
chunks.push(array.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
|
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
|
||||||
import Redis from "ioredis";
|
import Redis from "ioredis";
|
||||||
|
|
||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
TScanFullRepoEventPayload,
|
TScanFullRepoEventPayload,
|
||||||
@@ -32,7 +32,8 @@ export enum QueueName {
|
|||||||
SecretReplication = "secret-replication",
|
SecretReplication = "secret-replication",
|
||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
AccessTokenStatusUpdate = "access-token-status-update"
|
AccessTokenStatusUpdate = "access-token-status-update",
|
||||||
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@@ -56,7 +57,8 @@ export enum QueueJobs {
|
|||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
||||||
ServiceTokenStatusUpdate = "service-token-status-update"
|
ServiceTokenStatusUpdate = "service-token-status-update",
|
||||||
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -166,6 +168,19 @@ export type TQueueJobTypes = {
|
|||||||
name: QueueJobs.ProjectV3Migration;
|
name: QueueJobs.ProjectV3Migration;
|
||||||
payload: { projectId: string };
|
payload: { projectId: string };
|
||||||
};
|
};
|
||||||
|
[QueueName.ImportSecretsFromExternalSource]: {
|
||||||
|
name: QueueJobs.ImportSecretsFromExternalSource;
|
||||||
|
payload: {
|
||||||
|
actorEmail: string;
|
||||||
|
data: {
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
ciphertext: string;
|
||||||
|
algorithm: SecretEncryptionAlgo;
|
||||||
|
encoding: SecretKeyEncoding;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ import { certificateTemplateDALFactory } from "@app/services/certificate-templat
|
|||||||
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
||||||
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
|
||||||
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
||||||
@@ -1202,12 +1203,26 @@ export const registerRoutes = async (
|
|||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
const migrationService = externalMigrationServiceFactory({
|
const externalMigrationQueue = externalMigrationQueueFactory({
|
||||||
projectService,
|
|
||||||
orgService,
|
|
||||||
projectEnvService,
|
projectEnvService,
|
||||||
permissionService,
|
projectDAL,
|
||||||
secretService
|
projectService,
|
||||||
|
smtpService,
|
||||||
|
kmsService,
|
||||||
|
projectEnvDAL,
|
||||||
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretDAL: secretV2BridgeDAL,
|
||||||
|
queueService,
|
||||||
|
secretV2BridgeService
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrationService = externalMigrationServiceFactory({
|
||||||
|
externalMigrationQueue,
|
||||||
|
userDAL,
|
||||||
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
|
|||||||
@@ -4,22 +4,41 @@ import sjcl from "sjcl";
|
|||||||
import tweetnacl from "tweetnacl";
|
import tweetnacl from "tweetnacl";
|
||||||
import tweetnaclUtil from "tweetnacl-util";
|
import tweetnaclUtil from "tweetnacl-util";
|
||||||
|
|
||||||
import { OrgMembershipRole, ProjectMembershipRole, SecretType } from "@app/db/schemas";
|
import { SecretType } from "@app/db/schemas";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { chunkArray } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectServiceFactory } from "../project/project-service";
|
import { TProjectServiceFactory } from "../project/project-service";
|
||||||
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
||||||
import { TSecretServiceFactory } from "../secret/secret-service";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { fnSecretBulkInsert, getAllNestedSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
|
import type { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
|
||||||
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "./external-migration-types";
|
import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "./external-migration-types";
|
||||||
|
|
||||||
export type TImportDataIntoInfisicalDTO = {
|
export type TImportDataIntoInfisicalDTO = {
|
||||||
projectService: TProjectServiceFactory;
|
projectDAL: Pick<TProjectDALFactory, "transaction">;
|
||||||
orgService: TOrgServiceFactory;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
projectEnvService: TProjectEnvServiceFactory;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
secretService: TSecretServiceFactory;
|
|
||||||
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
|
||||||
|
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||||
|
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||||
|
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||||
|
|
||||||
input: TImportInfisicalDataCreate;
|
input: TImportInfisicalDataCreate;
|
||||||
};
|
};
|
||||||
@@ -46,13 +65,13 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
const parsedJson: TEnvKeyExportJSON = JSON.parse(decryptedJson) as TEnvKeyExportJSON;
|
const parsedJson: TEnvKeyExportJSON = JSON.parse(decryptedJson) as TEnvKeyExportJSON;
|
||||||
|
|
||||||
const infisicalImportData: InfisicalImportData = {
|
const infisicalImportData: InfisicalImportData = {
|
||||||
projects: new Map<string, { name: string; id: string }>(),
|
projects: [],
|
||||||
environments: new Map<string, { name: string; id: string; projectId: string }>(),
|
environments: [],
|
||||||
secrets: new Map<string, { name: string; id: string; projectId: string; environmentId: string; value: string }>()
|
secrets: []
|
||||||
};
|
};
|
||||||
|
|
||||||
parsedJson.apps.forEach((app: { name: string; id: string }) => {
|
parsedJson.apps.forEach((app: { name: string; id: string }) => {
|
||||||
infisicalImportData.projects.set(app.id, { name: app.name, id: app.id });
|
infisicalImportData.projects.push({ name: app.name, id: app.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
// string to string map for env templates
|
// string to string map for env templates
|
||||||
@@ -63,7 +82,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
|
|
||||||
// environments
|
// environments
|
||||||
for (const env of parsedJson.baseEnvironments) {
|
for (const env of parsedJson.baseEnvironments) {
|
||||||
infisicalImportData.environments?.set(env.id, {
|
infisicalImportData.environments.push({
|
||||||
id: env.id,
|
id: env.id,
|
||||||
name: envTemplates.get(env.environmentRoleId)!,
|
name: envTemplates.get(env.environmentRoleId)!,
|
||||||
projectId: env.envParentId
|
projectId: env.envParentId
|
||||||
@@ -75,9 +94,8 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
if (!env.includes("|")) {
|
if (!env.includes("|")) {
|
||||||
const envData = parsedJson.envs[env];
|
const envData = parsedJson.envs[env];
|
||||||
for (const secret of Object.keys(envData.variables)) {
|
for (const secret of Object.keys(envData.variables)) {
|
||||||
const id = randomUUID();
|
infisicalImportData.secrets.push({
|
||||||
infisicalImportData.secrets?.set(id, {
|
id: randomUUID(),
|
||||||
id,
|
|
||||||
name: secret,
|
name: secret,
|
||||||
environmentId: env,
|
environmentId: env,
|
||||||
value: envData.variables[secret].val
|
value: envData.variables[secret].val
|
||||||
@@ -91,9 +109,14 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
|
|
||||||
export const importDataIntoInfisicalFn = async ({
|
export const importDataIntoInfisicalFn = async ({
|
||||||
projectService,
|
projectService,
|
||||||
orgService,
|
projectEnvDAL,
|
||||||
projectEnvService,
|
projectDAL,
|
||||||
secretService,
|
secretDAL,
|
||||||
|
kmsService,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
folderDAL,
|
||||||
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
|
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
|
||||||
}: TImportDataIntoInfisicalDTO) => {
|
}: TImportDataIntoInfisicalDTO) => {
|
||||||
// Import data to infisical
|
// Import data to infisical
|
||||||
@@ -104,94 +127,132 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
const originalToNewProjectId = new Map<string, string>();
|
const originalToNewProjectId = new Map<string, string>();
|
||||||
const originalToNewEnvironmentId = new Map<string, string>();
|
const originalToNewEnvironmentId = new Map<string, string>();
|
||||||
|
|
||||||
for await (const [id, project] of data.projects) {
|
await projectDAL.transaction(async (tx) => {
|
||||||
const newProject = await projectService
|
for await (const project of data.projects) {
|
||||||
.createProject({
|
const newProject = await projectService
|
||||||
actor,
|
.createProject({
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
workspaceName: project.name,
|
|
||||||
createDefaultEnvs: false
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}] [id:${id}]` });
|
|
||||||
});
|
|
||||||
|
|
||||||
originalToNewProjectId.set(project.id, newProject.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invite user importing projects
|
|
||||||
const invites = await orgService.inviteUserToOrganization({
|
|
||||||
actorAuthMethod,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
inviteeEmails: [],
|
|
||||||
orgId: actorOrgId,
|
|
||||||
organizationRoleSlug: OrgMembershipRole.NoAccess,
|
|
||||||
projects: Array.from(originalToNewProjectId.values()).map((project) => ({
|
|
||||||
id: project,
|
|
||||||
projectRoleSlug: [ProjectMembershipRole.Member]
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
if (!invites) {
|
|
||||||
throw new BadRequestError({ message: `Failed to invite user to projects: [userId:${actorId}]` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import environments
|
|
||||||
if (data.environments) {
|
|
||||||
for await (const [id, environment] of data.environments) {
|
|
||||||
try {
|
|
||||||
const newEnvironment = await projectEnvService.createEnvironment({
|
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
name: environment.name,
|
workspaceName: project.name,
|
||||||
projectId: originalToNewProjectId.get(environment.projectId)!,
|
createDefaultEnvs: false,
|
||||||
slug: slugify(`${environment.name}-${alphaNumericNanoId(4)}`)
|
tx
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(e, `Failed to import to project [name:${project.name}]`);
|
||||||
|
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}]` });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!newEnvironment) {
|
originalToNewProjectId.set(project.id, newProject.id);
|
||||||
logger.error(`Failed to import environment: [name:${environment.name}] [id:${id}]`);
|
}
|
||||||
|
|
||||||
|
// Import environments
|
||||||
|
if (data.environments) {
|
||||||
|
for await (const environment of data.environments) {
|
||||||
|
const projectId = originalToNewProjectId.get(environment.projectId)!;
|
||||||
|
const slug = slugify(`${environment.name}-${alphaNumericNanoId(4)}`);
|
||||||
|
|
||||||
|
const existingEnv = await projectEnvDAL.findOne({ projectId, slug }, tx);
|
||||||
|
|
||||||
|
if (existingEnv) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `Failed to import environment: [name:${environment.name}] [id:${id}]`
|
message: `Environment with slug '${slug}' already exist`,
|
||||||
|
name: "CreateEnvironment"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
originalToNewEnvironmentId.set(id, newEnvironment.slug);
|
|
||||||
} catch (error) {
|
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
|
||||||
throw new BadRequestError({
|
const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx);
|
||||||
message: `Failed to import environment: ${environment.name}]`,
|
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
||||||
name: "EnvKeyMigrationImportEnvironment"
|
|
||||||
|
originalToNewEnvironmentId.set(environment.id, doc.slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.secrets && data.secrets.length > 0) {
|
||||||
|
const mappedToEnvironmentId = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
secretKey: string;
|
||||||
|
secretValue: string;
|
||||||
|
}[]
|
||||||
|
>();
|
||||||
|
|
||||||
|
for (const secret of data.secrets) {
|
||||||
|
if (!mappedToEnvironmentId.has(secret.environmentId)) {
|
||||||
|
mappedToEnvironmentId.set(secret.environmentId, []);
|
||||||
|
}
|
||||||
|
mappedToEnvironmentId.get(secret.environmentId)!.push({
|
||||||
|
secretKey: secret.name,
|
||||||
|
secretValue: secret.value || ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import secrets
|
// for each of the mappedEnvironmentId
|
||||||
if (data.secrets) {
|
for await (const [envId, secrets] of mappedToEnvironmentId) {
|
||||||
for await (const [id, secret] of data.secrets) {
|
const environment = data.environments.find((env) => env.id === envId);
|
||||||
const dataProjectId = data.environments?.get(secret.environmentId)?.projectId;
|
const projectId = originalToNewProjectId.get(environment?.projectId as string)!;
|
||||||
if (!dataProjectId) {
|
|
||||||
throw new BadRequestError({ message: `Failed to import secret "${secret.name}", project not found` });
|
if (!projectId) {
|
||||||
}
|
throw new BadRequestError({ message: `Failed to import secret, project not found` });
|
||||||
const projectId = originalToNewProjectId.get(dataProjectId);
|
}
|
||||||
const newSecret = await secretService.createSecretRaw({
|
|
||||||
actorId,
|
const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey(
|
||||||
actor,
|
{
|
||||||
actorOrgId,
|
type: KmsDataKey.SecretManager,
|
||||||
environment: originalToNewEnvironmentId.get(secret.environmentId)!,
|
projectId
|
||||||
actorAuthMethod,
|
},
|
||||||
projectId: projectId!,
|
tx
|
||||||
secretPath: "/",
|
);
|
||||||
secretName: secret.name,
|
|
||||||
type: SecretType.Shared,
|
const envSlug = originalToNewEnvironmentId.get(envId)!;
|
||||||
secretValue: secret.value || ""
|
const folder = await folderDAL.findBySecretPath(projectId, envSlug, "/", tx);
|
||||||
});
|
if (!folder)
|
||||||
if (!newSecret) {
|
throw new NotFoundError({
|
||||||
throw new BadRequestError({ message: `Failed to import secret: [name:${secret.name}] [id:${id}]` });
|
message: `Folder not found for the given environment slug (${envSlug}) & secret path (/)`,
|
||||||
|
name: "Create secret"
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretsByKeys = await secretDAL.findBySecretKeys(
|
||||||
|
folder.id,
|
||||||
|
secrets.map((el) => ({
|
||||||
|
key: el.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
if (secretsByKeys.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Secret already exist: ${secretsByKeys.map((el) => el.key).join(",")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretBatches = chunkArray(secrets, 2500);
|
||||||
|
for await (const secretBatch of secretBatches) {
|
||||||
|
await fnSecretBulkInsert({
|
||||||
|
inputSecrets: secretBatch.map((el) => {
|
||||||
|
const references = getAllNestedSecretReferences(el.secretValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 1,
|
||||||
|
encryptedValue: el.secretValue
|
||||||
|
? secretManagerEncrypt({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob
|
||||||
|
: undefined,
|
||||||
|
key: el.secretKey,
|
||||||
|
references,
|
||||||
|
type: SecretType.Shared
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
folderId: folder.id,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
tx
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { TProjectServiceFactory } from "../project/project-service";
|
||||||
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
|
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
||||||
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
|
||||||
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
|
import { importDataIntoInfisicalFn } from "./external-migration-fns";
|
||||||
|
import { ExternalPlatforms, TImportInfisicalDataCreate } from "./external-migration-types";
|
||||||
|
|
||||||
|
export type TExternalMigrationQueueFactoryDep = {
|
||||||
|
smtpService: TSmtpService;
|
||||||
|
queueService: TQueueServiceFactory;
|
||||||
|
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "transaction">;
|
||||||
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|
||||||
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
|
||||||
|
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||||
|
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||||
|
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
|
||||||
|
|
||||||
|
export const externalMigrationQueueFactory = ({
|
||||||
|
queueService,
|
||||||
|
projectService,
|
||||||
|
smtpService,
|
||||||
|
projectDAL,
|
||||||
|
projectEnvService,
|
||||||
|
secretV2BridgeService,
|
||||||
|
kmsService,
|
||||||
|
projectEnvDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
folderDAL
|
||||||
|
}: TExternalMigrationQueueFactoryDep) => {
|
||||||
|
const startImport = async (dto: {
|
||||||
|
actorEmail: string;
|
||||||
|
data: {
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
ciphertext: string;
|
||||||
|
algorithm: SecretEncryptionAlgo;
|
||||||
|
encoding: SecretKeyEncoding;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.ImportSecretsFromExternalSource,
|
||||||
|
QueueJobs.ImportSecretsFromExternalSource,
|
||||||
|
dto,
|
||||||
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.ImportSecretsFromExternalSource, async (job) => {
|
||||||
|
try {
|
||||||
|
const { data, actorEmail } = job.data;
|
||||||
|
|
||||||
|
await smtpService.sendMail({
|
||||||
|
recipients: [actorEmail],
|
||||||
|
subjectLine: "Infisical import started",
|
||||||
|
substitutions: {
|
||||||
|
provider: ExternalPlatforms.EnvKey
|
||||||
|
},
|
||||||
|
template: SmtpTemplates.ExternalImportStarted
|
||||||
|
});
|
||||||
|
|
||||||
|
const decrypted = infisicalSymmetricDecrypt({
|
||||||
|
ciphertext: data.ciphertext,
|
||||||
|
iv: data.iv,
|
||||||
|
keyEncoding: data.encoding,
|
||||||
|
tag: data.tag
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedJson = JSON.parse(decrypted) as TImportInfisicalDataCreate;
|
||||||
|
|
||||||
|
await importDataIntoInfisicalFn({
|
||||||
|
input: decryptedJson,
|
||||||
|
projectDAL,
|
||||||
|
projectEnvDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
folderDAL,
|
||||||
|
kmsService,
|
||||||
|
projectService,
|
||||||
|
projectEnvService,
|
||||||
|
secretV2BridgeService
|
||||||
|
});
|
||||||
|
|
||||||
|
await smtpService.sendMail({
|
||||||
|
recipients: [actorEmail],
|
||||||
|
subjectLine: "Infisical import successful",
|
||||||
|
substitutions: {
|
||||||
|
provider: ExternalPlatforms.EnvKey
|
||||||
|
},
|
||||||
|
template: SmtpTemplates.ExternalImportSuccessful
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
await smtpService.sendMail({
|
||||||
|
recipients: [job.data.actorEmail],
|
||||||
|
subjectLine: "Infisical import failed",
|
||||||
|
substitutions: {
|
||||||
|
provider: ExternalPlatforms.EnvKey,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
||||||
|
error: (err as any)?.message || "Unknown error"
|
||||||
|
},
|
||||||
|
template: SmtpTemplates.ExternalImportFailed
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.error(err, "Failed to import data from external source");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
startImport
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,30 +1,25 @@
|
|||||||
import { OrgMembershipRole } from "@app/db/schemas";
|
import { OrgMembershipRole } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { ForbiddenRequestError } from "@app/lib/errors";
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectServiceFactory } from "../project/project-service";
|
import { decryptEnvKeyDataFn, parseEnvKeyDataFn } from "./external-migration-fns";
|
||||||
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
import { TExternalMigrationQueueFactory } from "./external-migration-queue";
|
||||||
import { TSecretServiceFactory } from "../secret/secret-service";
|
|
||||||
import { decryptEnvKeyDataFn, importDataIntoInfisicalFn, parseEnvKeyDataFn } from "./external-migration-fns";
|
|
||||||
import { TImportEnvKeyDataCreate } from "./external-migration-types";
|
import { TImportEnvKeyDataCreate } from "./external-migration-types";
|
||||||
|
|
||||||
type TExternalMigrationServiceFactoryDep = {
|
type TExternalMigrationServiceFactoryDep = {
|
||||||
projectService: TProjectServiceFactory;
|
|
||||||
orgService: TOrgServiceFactory;
|
|
||||||
projectEnvService: TProjectEnvServiceFactory;
|
|
||||||
secretService: TSecretServiceFactory;
|
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
|
externalMigrationQueue: TExternalMigrationQueueFactory;
|
||||||
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TExternalMigrationServiceFactory = ReturnType<typeof externalMigrationServiceFactory>;
|
export type TExternalMigrationServiceFactory = ReturnType<typeof externalMigrationServiceFactory>;
|
||||||
|
|
||||||
export const externalMigrationServiceFactory = ({
|
export const externalMigrationServiceFactory = ({
|
||||||
projectService,
|
|
||||||
orgService,
|
|
||||||
projectEnvService,
|
|
||||||
permissionService,
|
permissionService,
|
||||||
secretService
|
externalMigrationQueue,
|
||||||
|
userDAL
|
||||||
}: TExternalMigrationServiceFactoryDep) => {
|
}: TExternalMigrationServiceFactoryDep) => {
|
||||||
const importEnvKeyData = async ({
|
const importEnvKeyData = async ({
|
||||||
decryptionKey,
|
decryptionKey,
|
||||||
@@ -41,21 +36,28 @@ export const externalMigrationServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (membership.role !== OrgMembershipRole.Admin) {
|
if (membership.role !== OrgMembershipRole.Admin) {
|
||||||
throw new ForbiddenRequestError({ message: "Only admins can import data" });
|
throw new ForbiddenRequestError({ message: "Only admins can import data" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = await userDAL.findById(actorId);
|
||||||
const json = await decryptEnvKeyDataFn(decryptionKey, encryptedJson);
|
const json = await decryptEnvKeyDataFn(decryptionKey, encryptedJson);
|
||||||
const envKeyData = await parseEnvKeyDataFn(json);
|
const envKeyData = await parseEnvKeyDataFn(json);
|
||||||
const response = await importDataIntoInfisicalFn({
|
|
||||||
input: { data: envKeyData, actor, actorId, actorOrgId, actorAuthMethod },
|
const stringifiedJson = JSON.stringify({
|
||||||
projectService,
|
data: envKeyData,
|
||||||
orgService,
|
actor,
|
||||||
projectEnvService,
|
actorId,
|
||||||
secretService
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
const encrypted = infisicalSymmetricEncypt(stringifiedJson);
|
||||||
|
|
||||||
|
await externalMigrationQueue.startImport({
|
||||||
|
actorEmail: user.email!,
|
||||||
|
data: encrypted
|
||||||
});
|
});
|
||||||
return response;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,26 +1,9 @@
|
|||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||||
|
|
||||||
export type InfisicalImportData = {
|
export type InfisicalImportData = {
|
||||||
projects: Map<string, { name: string; id: string }>;
|
projects: Array<{ name: string; id: string }>;
|
||||||
|
environments: Array<{ name: string; id: string; projectId: string }>;
|
||||||
environments?: Map<
|
secrets: Array<{ name: string; id: string; environmentId: string; value: string }>;
|
||||||
string,
|
|
||||||
{
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
secrets?: Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
environmentId: string;
|
|
||||||
value?: string;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TImportEnvKeyDataCreate = {
|
export type TImportEnvKeyDataCreate = {
|
||||||
@@ -104,3 +87,7 @@ export type TEnvKeyExportJSON = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum ExternalPlatforms {
|
||||||
|
EnvKey = "EnvKey"
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,8 +160,8 @@ export const kmsServiceFactory = ({
|
|||||||
* In mean time the rest of the request will wait until creation is finished followed by getting the created on
|
* In mean time the rest of the request will wait until creation is finished followed by getting the created on
|
||||||
* In real time this would be milliseconds
|
* In real time this would be milliseconds
|
||||||
*/
|
*/
|
||||||
const getOrgKmsKeyId = async (orgId: string) => {
|
const getOrgKmsKeyId = async (orgId: string, trx?: Knex) => {
|
||||||
let org = await orgDAL.findById(orgId);
|
let org = await orgDAL.findById(orgId, trx);
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({ message: "Org not found" });
|
throw new NotFoundError({ message: "Org not found" });
|
||||||
@@ -180,9 +180,9 @@ export const kmsServiceFactory = ({
|
|||||||
waitingCb: () => logger.info("KMS. Waiting for org key to be created")
|
waitingCb: () => logger.info("KMS. Waiting for org key to be created")
|
||||||
});
|
});
|
||||||
|
|
||||||
org = await orgDAL.findById(orgId);
|
org = await orgDAL.findById(orgId, trx);
|
||||||
} else {
|
} else {
|
||||||
const keyId = await orgDAL.transaction(async (tx) => {
|
const keyId = await (trx || orgDAL).transaction(async (tx) => {
|
||||||
org = await orgDAL.findById(orgId, tx);
|
org = await orgDAL.findById(orgId, tx);
|
||||||
if (org.kmsDefaultKeyId) {
|
if (org.kmsDefaultKeyId) {
|
||||||
return org.kmsDefaultKeyId;
|
return org.kmsDefaultKeyId;
|
||||||
@@ -240,11 +240,12 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const decryptWithKmsKey = async ({
|
const decryptWithKmsKey = async ({
|
||||||
kmsId,
|
kmsId,
|
||||||
depth = 0
|
depth = 0,
|
||||||
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { depth?: number }) => {
|
tx
|
||||||
|
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { depth?: number; tx?: Knex }) => {
|
||||||
if (depth > 2) throw new BadRequestError({ message: "KMS depth max limit" });
|
if (depth > 2) throw new BadRequestError({ message: "KMS depth max limit" });
|
||||||
|
|
||||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
|
||||||
if (!kmsDoc) {
|
if (!kmsDoc) {
|
||||||
throw new NotFoundError({ message: "KMS ID not found" });
|
throw new NotFoundError({ message: "KMS ID not found" });
|
||||||
}
|
}
|
||||||
@@ -261,7 +262,8 @@ export const kmsServiceFactory = ({
|
|||||||
// we put a limit of depth to avoid too many cycles
|
// we put a limit of depth to avoid too many cycles
|
||||||
const orgKmsDecryptor = await decryptWithKmsKey({
|
const orgKmsDecryptor = await decryptWithKmsKey({
|
||||||
kmsId: kmsDoc.orgKms.id,
|
kmsId: kmsDoc.orgKms.id,
|
||||||
depth: depth + 1
|
depth: depth + 1,
|
||||||
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgKmsDataKey = await orgKmsDecryptor({
|
const orgKmsDataKey = await orgKmsDecryptor({
|
||||||
@@ -375,9 +377,9 @@ export const kmsServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const $getOrgKmsDataKey = async (orgId: string) => {
|
const $getOrgKmsDataKey = async (orgId: string, trx?: Knex) => {
|
||||||
const kmsKeyId = await getOrgKmsKeyId(orgId);
|
const kmsKeyId = await getOrgKmsKeyId(orgId, trx);
|
||||||
let org = await orgDAL.findById(orgId);
|
let org = await orgDAL.findById(orgId, trx);
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({ message: "Org not found" });
|
throw new NotFoundError({ message: "Org not found" });
|
||||||
@@ -396,9 +398,9 @@ export const kmsServiceFactory = ({
|
|||||||
waitingCb: () => logger.info("KMS. Waiting for org data key to be created")
|
waitingCb: () => logger.info("KMS. Waiting for org data key to be created")
|
||||||
});
|
});
|
||||||
|
|
||||||
org = await orgDAL.findById(orgId);
|
org = await orgDAL.findById(orgId, trx);
|
||||||
} else {
|
} else {
|
||||||
const orgDataKey = await orgDAL.transaction(async (tx) => {
|
const orgDataKey = await (trx || orgDAL).transaction(async (tx) => {
|
||||||
org = await orgDAL.findById(orgId, tx);
|
org = await orgDAL.findById(orgId, tx);
|
||||||
if (org.kmsEncryptedDataKey) {
|
if (org.kmsEncryptedDataKey) {
|
||||||
return;
|
return;
|
||||||
@@ -455,8 +457,8 @@ export const kmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
const getProjectSecretManagerKmsKeyId = async (projectId: string, trx?: Knex) => {
|
||||||
let project = await projectDAL.findById(projectId);
|
let project = await projectDAL.findById(projectId, trx);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new NotFoundError({ message: "Project not found" });
|
throw new NotFoundError({ message: "Project not found" });
|
||||||
}
|
}
|
||||||
@@ -477,7 +479,7 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
project = await projectDAL.findById(projectId);
|
project = await projectDAL.findById(projectId);
|
||||||
} else {
|
} else {
|
||||||
const kmsKeyId = await projectDAL.transaction(async (tx) => {
|
const kmsKeyId = await (trx || projectDAL).transaction(async (tx) => {
|
||||||
project = await projectDAL.findById(projectId, tx);
|
project = await projectDAL.findById(projectId, tx);
|
||||||
if (project.kmsSecretManagerKeyId) {
|
if (project.kmsSecretManagerKeyId) {
|
||||||
return project.kmsSecretManagerKeyId;
|
return project.kmsSecretManagerKeyId;
|
||||||
@@ -520,9 +522,9 @@ export const kmsServiceFactory = ({
|
|||||||
return project.kmsSecretManagerKeyId;
|
return project.kmsSecretManagerKeyId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const $getProjectSecretManagerKmsDataKey = async (projectId: string) => {
|
const $getProjectSecretManagerKmsDataKey = async (projectId: string, trx?: Knex) => {
|
||||||
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId);
|
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId, trx);
|
||||||
let project = await projectDAL.findById(projectId);
|
let project = await projectDAL.findById(projectId, trx);
|
||||||
|
|
||||||
if (!project.kmsSecretManagerEncryptedDataKey) {
|
if (!project.kmsSecretManagerEncryptedDataKey) {
|
||||||
const lock = await keyStore
|
const lock = await keyStore
|
||||||
@@ -538,18 +540,21 @@ export const kmsServiceFactory = ({
|
|||||||
delay: 500
|
delay: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
project = await projectDAL.findById(projectId);
|
project = await projectDAL.findById(projectId, trx);
|
||||||
} else {
|
} else {
|
||||||
const projectDataKey = await projectDAL.transaction(async (tx) => {
|
const projectDataKey = await (trx || projectDAL).transaction(async (tx) => {
|
||||||
project = await projectDAL.findById(projectId, tx);
|
project = await projectDAL.findById(projectId, tx);
|
||||||
if (project.kmsSecretManagerEncryptedDataKey) {
|
if (project.kmsSecretManagerEncryptedDataKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataKey = randomSecureBytes();
|
const dataKey = randomSecureBytes();
|
||||||
const kmsEncryptor = await encryptWithKmsKey({
|
const kmsEncryptor = await encryptWithKmsKey(
|
||||||
kmsId: kmsKeyId
|
{
|
||||||
});
|
kmsId: kmsKeyId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
const { cipherTextBlob } = await kmsEncryptor({
|
const { cipherTextBlob } = await kmsEncryptor({
|
||||||
plainText: dataKey
|
plainText: dataKey
|
||||||
@@ -585,7 +590,8 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const kmsDecryptor = await decryptWithKmsKey({
|
const kmsDecryptor = await decryptWithKmsKey({
|
||||||
kmsId: kmsKeyId
|
kmsId: kmsKeyId,
|
||||||
|
tx: trx
|
||||||
});
|
});
|
||||||
|
|
||||||
return kmsDecryptor({
|
return kmsDecryptor({
|
||||||
@@ -593,13 +599,13 @@ export const kmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO) => {
|
const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
|
||||||
switch (dto.type) {
|
switch (dto.type) {
|
||||||
case KmsDataKey.SecretManager: {
|
case KmsDataKey.SecretManager: {
|
||||||
return $getProjectSecretManagerKmsDataKey(dto.projectId);
|
return $getProjectSecretManagerKmsDataKey(dto.projectId, trx);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return $getOrgKmsDataKey(dto.orgId);
|
return $getOrgKmsDataKey(dto.orgId, trx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -607,8 +613,9 @@ export const kmsServiceFactory = ({
|
|||||||
// by keeping the decrypted data key in inner scope
|
// by keeping the decrypted data key in inner scope
|
||||||
// none of the entities outside can interact directly or expose the data key
|
// none of the entities outside can interact directly or expose the data key
|
||||||
// NOTICE: If changing here update migrations/utils/kms
|
// NOTICE: If changing here update migrations/utils/kms
|
||||||
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO) => {
|
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
|
||||||
const dataKey = await $getDataKey(encryptionContext);
|
const dataKey = await $getDataKey(encryptionContext, trx);
|
||||||
|
|
||||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -26,18 +26,13 @@ export type TDeleteOrgMembershipDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TInviteUserToOrgDTO = {
|
export type TInviteUserToOrgDTO = {
|
||||||
actorId: string;
|
|
||||||
actor: ActorType;
|
|
||||||
orgId: string;
|
|
||||||
actorOrgId: string | undefined;
|
|
||||||
actorAuthMethod: ActorAuthMethod;
|
|
||||||
inviteeEmails: string[];
|
inviteeEmails: string[];
|
||||||
organizationRoleSlug: string;
|
organizationRoleSlug: string;
|
||||||
projects?: {
|
projects?: {
|
||||||
id: string;
|
id: string;
|
||||||
projectRoleSlug?: string[];
|
projectRoleSlug?: string[];
|
||||||
}[];
|
}[];
|
||||||
};
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TVerifyUserToOrgDTO = {
|
export type TVerifyUserToOrgDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ export const projectServiceFactory = ({
|
|||||||
workspaceName,
|
workspaceName,
|
||||||
slug: projectSlug,
|
slug: projectSlug,
|
||||||
kmsKeyId,
|
kmsKeyId,
|
||||||
|
tx: trx,
|
||||||
createDefaultEnvs = true
|
createDefaultEnvs = true
|
||||||
}: TCreateProjectDTO) => {
|
}: TCreateProjectDTO) => {
|
||||||
const organization = await orgDAL.findOne({ id: actorOrgId });
|
const organization = await orgDAL.findOne({ id: actorOrgId });
|
||||||
@@ -169,7 +170,7 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await projectDAL.transaction(async (tx) => {
|
const results = await (trx || projectDAL).transaction(async (tx) => {
|
||||||
const ghostUser = await orgService.addGhostUser(organization.id, tx);
|
const ghostUser = await orgService.addGhostUser(organization.id, tx);
|
||||||
|
|
||||||
if (kmsKeyId) {
|
if (kmsKeyId) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TProjectKeys } from "@app/db/schemas";
|
import { TProjectKeys } from "@app/db/schemas";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ export type TCreateProjectDTO = {
|
|||||||
slug?: string;
|
slug?: string;
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
createDefaultEnvs?: boolean;
|
createDefaultEnvs?: boolean;
|
||||||
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteProjectBySlugDTO = {
|
export type TDeleteProjectBySlugDTO = {
|
||||||
|
|||||||
@@ -82,7 +82,10 @@ export const fnSecretBulkInsert = async ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const newSecrets = await secretDAL.insertMany(sanitizedInputSecrets.map((el) => ({ ...el, folderId })));
|
const newSecrets = await secretDAL.insertMany(
|
||||||
|
sanitizedInputSecrets.map((el) => ({ ...el, folderId })),
|
||||||
|
tx
|
||||||
|
);
|
||||||
const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key);
|
const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key);
|
||||||
const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) =>
|
const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) =>
|
||||||
secretTags.map((tag) => ({
|
secretTags.map((tag) => ({
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ type TSecretQueueFactoryDep = {
|
|||||||
secretTagDAL: TSecretTagDALFactory;
|
secretTagDAL: TSecretTagDALFactory;
|
||||||
userDAL: Pick<TUserDALFactory, "findById">;
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
secretVersionTagDAL: TSecretVersionTagDALFactory;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: TKmsServiceFactory;
|
||||||
secretV2BridgeDAL: TSecretV2BridgeDALFactory;
|
secretV2BridgeDAL: TSecretV2BridgeDALFactory;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "batchInsert" | "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "batchInsert" | "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "batchInsert">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "batchInsert">;
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ export enum SmtpTemplates {
|
|||||||
WorkspaceInvite = "workspaceInvitation.handlebars",
|
WorkspaceInvite = "workspaceInvitation.handlebars",
|
||||||
ScimUserProvisioned = "scimUserProvisioned.handlebars",
|
ScimUserProvisioned = "scimUserProvisioned.handlebars",
|
||||||
PkiExpirationAlert = "pkiExpirationAlert.handlebars",
|
PkiExpirationAlert = "pkiExpirationAlert.handlebars",
|
||||||
IntegrationSyncFailed = "integrationSyncFailed.handlebars"
|
IntegrationSyncFailed = "integrationSyncFailed.handlebars",
|
||||||
|
ExternalImportSuccessful = "externalImportSuccessful.handlebars",
|
||||||
|
ExternalImportFailed = "externalImportFailed.handlebars",
|
||||||
|
ExternalImportStarted = "externalImportStarted.handlebars"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SmtpHost {
|
export enum SmtpHost {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
|
<title>Import failed</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>An import from {{provider}} to Infisical has failed</h2>
|
||||||
|
<p>An import from
|
||||||
|
{{provider}}
|
||||||
|
to Infisical has failed due to unforeseen circumstances. Please re-try your import, and if the issue persists, you
|
||||||
|
can contact the Infisical team at [email protected].
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Error: {{error}}</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
|
<title>Import in progress</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>An import from {{provider}} to Infisical is in progress</h2>
|
||||||
|
<p>An import from
|
||||||
|
{{provider}}
|
||||||
|
to Infisical is in progress. The import process may take up to 30 minutes, and you will receive once the import
|
||||||
|
has finished or if it fails.</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
|
<title>Import successful</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h2>An import from {{provider}} to Infisical was successful</h2>
|
||||||
|
<p>An import from {{provider}} was successful. Your data is now available in Infisical.</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -18,11 +18,10 @@ export const useImportEnvKey = () => {
|
|||||||
};
|
};
|
||||||
decryptionKey: string;
|
decryptionKey: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v3/migrate/env-key/", {
|
await apiRequest.post("/api/v3/migrate/env-key/", {
|
||||||
encryptedJson,
|
encryptedJson,
|
||||||
decryptionKey
|
decryptionKey
|
||||||
});
|
});
|
||||||
return data;
|
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
||||||
|
|||||||
+3
-2
@@ -54,8 +54,9 @@ export const EnvKeyPlatformModal = ({ onClose }: Props) => {
|
|||||||
decryptionKey: data.encryptionKey
|
decryptionKey: data.encryptionKey
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Data imported successfully.",
|
title: "Import started",
|
||||||
type: "success"
|
text: "Your data is being imported. You will receive an email when the import is complete or if the import fails. This may take up to 10 minutes.",
|
||||||
|
type: "info"
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
Reference in New Issue
Block a user