mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 08:27:36 +00:00
feat: envkey data migration refactor
This commit is contained in:
@@ -4,7 +4,7 @@ import sjcl from "sjcl";
|
|||||||
import tweetnacl from "tweetnacl";
|
import tweetnacl from "tweetnacl";
|
||||||
import tweetnaclUtil from "tweetnacl-util";
|
import tweetnaclUtil from "tweetnacl-util";
|
||||||
|
|
||||||
import { SecretType } from "@app/db/schemas";
|
import { SecretType, TSecretFolders } from "@app/db/schemas";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { chunkArray } from "@app/lib/fn";
|
import { chunkArray } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -35,7 +35,7 @@ export type TImportDataIntoInfisicalDTO = {
|
|||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">;
|
||||||
projectService: Pick<TProjectServiceFactory, "createProject">;
|
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||||
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||||
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||||
@@ -67,6 +67,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
const infisicalImportData: InfisicalImportData = {
|
const infisicalImportData: InfisicalImportData = {
|
||||||
projects: [],
|
projects: [],
|
||||||
environments: [],
|
environments: [],
|
||||||
|
folders: [],
|
||||||
secrets: []
|
secrets: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,25 +81,96 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
envTemplates.set(env.id, env.defaultName);
|
envTemplates.set(env.id, env.defaultName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// environments
|
// custom base environments
|
||||||
for (const env of parsedJson.baseEnvironments) {
|
for (const env of parsedJson.nonDefaultEnvironmentRoles) {
|
||||||
infisicalImportData.environments.push({
|
envTemplates.set(env.id, env.name);
|
||||||
id: env.id,
|
|
||||||
name: envTemplates.get(env.environmentRoleId)!,
|
|
||||||
projectId: env.envParentId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// secrets
|
// environments
|
||||||
|
for (const env of parsedJson.baseEnvironments) {
|
||||||
|
const app = parsedJson.apps.find((a) => a.id === env.envParentId);
|
||||||
|
|
||||||
|
// If we find the app from the envParentId, we know this is a root-level environment.
|
||||||
|
if (app) {
|
||||||
|
infisicalImportData.environments.push({
|
||||||
|
id: env.id,
|
||||||
|
name: envTemplates.get(env.environmentRoleId)!,
|
||||||
|
projectId: app.id
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// const parentBlock = parsedJson.blocks.find((b) => b.id === env.envParentId);
|
||||||
|
// // If this is found, then we know this is a sub-environment. The `parentEnvironment` is the sub environment.
|
||||||
|
// const subEnvironment = parsedJson.subEnvironments.find(
|
||||||
|
// (s) => s.parentEnvironmentId === env.id && parsedJson.apps.find((a) => a.id === s.envParentId)
|
||||||
|
// );
|
||||||
|
// if (subEnvironment) {
|
||||||
|
// infisicalImportData.folders.push({
|
||||||
|
// name: subEnvironment.subName,
|
||||||
|
// parentFolderId: subEnvironment.parentEnvironmentId,
|
||||||
|
// environmentId: env.id,
|
||||||
|
// id: subEnvironment.id
|
||||||
|
// });
|
||||||
|
// } else if (parentBlock) {
|
||||||
|
// // TODO(daniel): Find a way to get the secrets from the parent block, so we can later insert it
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subEnv of parsedJson.subEnvironments) {
|
||||||
|
// this will only find the app if the subEnv is a branch, not a block.
|
||||||
|
const app = parsedJson.apps.find((a) => a.id === subEnv.envParentId);
|
||||||
|
|
||||||
|
const parentEnvironment = infisicalImportData.environments.find((e) => e.id === subEnv.parentEnvironmentId);
|
||||||
|
|
||||||
|
if (app) {
|
||||||
|
infisicalImportData.folders.push({
|
||||||
|
name: subEnv.subName,
|
||||||
|
parentFolderId: subEnv.parentEnvironmentId,
|
||||||
|
environmentId: parentEnvironment!.id,
|
||||||
|
id: subEnv.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// secrets with/without inheritance
|
||||||
for (const env of Object.keys(parsedJson.envs)) {
|
for (const env of Object.keys(parsedJson.envs)) {
|
||||||
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 selectedSecret = envData.variables[secret];
|
||||||
|
|
||||||
|
if (selectedSecret.inheritsEnvironmentId) {
|
||||||
|
const findRootInheritedSecret = (currentSecret: { val?: string; inheritsEnvironmentId?: string }) => {
|
||||||
|
if (currentSecret.inheritsEnvironmentId) {
|
||||||
|
const inheritedSecret = parsedJson.envs[currentSecret.inheritsEnvironmentId].variables[secret];
|
||||||
|
if (inheritedSecret) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
currentSecret.val = inheritedSecret.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
findRootInheritedSecret(inheritedSecret);
|
||||||
|
}
|
||||||
|
return currentSecret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sec = findRootInheritedSecret(selectedSecret);
|
||||||
|
|
||||||
|
infisicalImportData.secrets.push({
|
||||||
|
id: randomUUID(),
|
||||||
|
name: secret,
|
||||||
|
environmentId: env,
|
||||||
|
value: sec.val || "???"
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
infisicalImportData.secrets.push({
|
infisicalImportData.secrets.push({
|
||||||
id: randomUUID(),
|
id: randomUUID(),
|
||||||
name: secret,
|
name: secret,
|
||||||
environmentId: env,
|
environmentId: env,
|
||||||
value: envData.variables[secret].val
|
value: selectedSecret.val || "???_???"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +197,17 @@ 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,
|
||||||
|
{ envId: string; envSlug: string; rootFolderId: string; projectId: string }
|
||||||
|
>();
|
||||||
|
const originalToNewFolderId = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
folderId: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
|
>();
|
||||||
const projectsNotImported: string[] = [];
|
const projectsNotImported: string[] = [];
|
||||||
|
|
||||||
await projectDAL.transaction(async (tx) => {
|
await projectDAL.transaction(async (tx) => {
|
||||||
@@ -170,12 +252,46 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
|
|
||||||
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
|
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
|
||||||
const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx);
|
const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx);
|
||||||
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
const folder = await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
||||||
|
|
||||||
originalToNewEnvironmentId.set(environment.id, doc.slug);
|
originalToNewEnvironmentId.set(environment.id, {
|
||||||
|
envSlug: doc.slug,
|
||||||
|
envId: doc.id,
|
||||||
|
rootFolderId: folder.id,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.folders) {
|
||||||
|
for await (const folder of data.folders) {
|
||||||
|
const parentEnv = originalToNewEnvironmentId.get(folder.parentFolderId as string);
|
||||||
|
|
||||||
|
if (!parentEnv) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFolder = await folderDAL.create(
|
||||||
|
{
|
||||||
|
name: folder.name,
|
||||||
|
envId: parentEnv.envId,
|
||||||
|
parentId: parentEnv.rootFolderId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
originalToNewFolderId.set(folder.id, {
|
||||||
|
folderId: newFolder.id,
|
||||||
|
projectId: parentEnv.projectId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("data.folders", data.folders);
|
||||||
|
|
||||||
|
console.log("data.secrets", data.secrets);
|
||||||
|
|
||||||
if (data.secrets && data.secrets.length > 0) {
|
if (data.secrets && data.secrets.length > 0) {
|
||||||
const mappedToEnvironmentId = new Map<
|
const mappedToEnvironmentId = new Map<
|
||||||
string,
|
string,
|
||||||
@@ -186,7 +302,7 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
>();
|
>();
|
||||||
|
|
||||||
for (const secret of data.secrets) {
|
for (const secret of data.secrets) {
|
||||||
if (!originalToNewEnvironmentId.get(secret.environmentId)) {
|
if (!originalToNewEnvironmentId.get(secret.environmentId) && !originalToNewFolderId.get(secret.environmentId)) {
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -202,33 +318,68 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
|
|
||||||
// for each of the mappedEnvironmentId
|
// for each of the mappedEnvironmentId
|
||||||
for await (const [envId, secrets] of mappedToEnvironmentId) {
|
for await (const [envId, secrets] of mappedToEnvironmentId) {
|
||||||
const environment = data.environments.find((env) => env.id === envId);
|
console.log(`envId ${envId} secrets:`, secrets);
|
||||||
const projectId = originalToNewProjectId.get(environment?.projectId as string)!;
|
|
||||||
|
|
||||||
if (!projectId) {
|
const environment = data.environments.find((env) => env.id === envId);
|
||||||
throw new BadRequestError({ message: `Failed to import secret, project not found` });
|
const foundFolder = originalToNewFolderId.get(envId);
|
||||||
|
|
||||||
|
console.log(`FOUND FOLDER BY ENV.ID ${envId}`, foundFolder);
|
||||||
|
|
||||||
|
let selectedFolder: TSecretFolders | undefined;
|
||||||
|
let selectedProjectId: string | undefined;
|
||||||
|
if (foundFolder) {
|
||||||
|
console.log("RUNNING FOLDER HANDLER");
|
||||||
|
|
||||||
|
selectedFolder = await folderDAL.findById(foundFolder.folderId, tx);
|
||||||
|
selectedProjectId = foundFolder.projectId;
|
||||||
|
} else if (environment) {
|
||||||
|
console.log("RUNNING ENVIRONMENT HANDLER");
|
||||||
|
const projectId = originalToNewProjectId.get(environment.projectId)!;
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
throw new BadRequestError({ message: `Failed to import secret, project not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = originalToNewEnvironmentId.get(envId)!;
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, env.envSlug, "/", tx);
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder not found for the given environment slug (${env.envSlug}) & secret path (/)`,
|
||||||
|
name: "Create secret"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedFolder = folder;
|
||||||
|
selectedProjectId = projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedFolder) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder not found for the given environment slug & secret path`,
|
||||||
|
name: "CreateSecret"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedProjectId) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Project not found for the given environment slug & secret path`,
|
||||||
|
name: "CreateSecret"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey(
|
const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey(
|
||||||
{
|
{
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId: selectedProjectId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
const envSlug = originalToNewEnvironmentId.get(envId)!;
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, envSlug, "/", tx);
|
|
||||||
if (!folder)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Folder not found for the given environment slug (${envSlug}) & secret path (/)`,
|
|
||||||
name: "Create secret"
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretBatches = chunkArray(secrets, 2500);
|
const secretBatches = chunkArray(secrets, 2500);
|
||||||
for await (const secretBatch of secretBatches) {
|
for await (const secretBatch of secretBatches) {
|
||||||
const secretsByKeys = await secretDAL.findBySecretKeys(
|
const secretsByKeys = await secretDAL.findBySecretKeys(
|
||||||
folder.id,
|
selectedFolder.id,
|
||||||
secretBatch.map((el) => ({
|
secretBatch.map((el) => ({
|
||||||
key: el.secretKey,
|
key: el.secretKey,
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
@@ -254,7 +405,7 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
folderId: folder.id,
|
folderId: selectedFolder.id,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
|||||||
export type InfisicalImportData = {
|
export type InfisicalImportData = {
|
||||||
projects: Array<{ name: string; id: string }>;
|
projects: Array<{ name: string; id: string }>;
|
||||||
environments: Array<{ name: string; id: string; projectId: string }>;
|
environments: Array<{ name: string; id: string; projectId: string }>;
|
||||||
secrets: Array<{ name: string; id: string; environmentId: string; value: string }>;
|
folders: Array<{ id: string; name: string; environmentId: string; parentFolderId?: string }>;
|
||||||
|
secrets: Array<{ id: string; name: string; environmentId: string; value: string; folderId?: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TImportEnvKeyDataCreate = {
|
export type TImportEnvKeyDataCreate = {
|
||||||
@@ -28,62 +29,63 @@ export type TEnvKeyExportJSON = {
|
|||||||
org: {
|
org: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
settings: {
|
// settings, which we dont care about
|
||||||
auth: {
|
|
||||||
inviteExpirationMs: number;
|
|
||||||
deviceGrantExpirationMs: number;
|
|
||||||
tokenExpirationMs: number;
|
|
||||||
};
|
|
||||||
crypto: {
|
|
||||||
requiresPassphrase: boolean;
|
|
||||||
requiresLockout: boolean;
|
|
||||||
};
|
|
||||||
envs: {
|
|
||||||
autoCaps: boolean;
|
|
||||||
autoCommitLocals: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apps are projects
|
||||||
apps: {
|
apps: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
settings: Record<string, unknown>;
|
|
||||||
}[];
|
}[];
|
||||||
defaultOrgRoles: {
|
// Blocks are basically global projects that can be imported in other projects
|
||||||
|
blocks: {
|
||||||
id: string;
|
id: string;
|
||||||
defaultName: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
defaultAppRoles: {
|
|
||||||
id: string;
|
appBlocks: {
|
||||||
defaultName: string;
|
appId: string;
|
||||||
|
blockId: string;
|
||||||
|
orderIndex: number;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
defaultEnvironmentRoles: {
|
defaultEnvironmentRoles: {
|
||||||
id: string;
|
id: string;
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
settings: {
|
|
||||||
autoCommit: boolean;
|
|
||||||
};
|
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
nonDefaultEnvironmentRoles: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
baseEnvironments: {
|
baseEnvironments: {
|
||||||
id: string;
|
id: string;
|
||||||
envParentId: string;
|
envParentId: string;
|
||||||
environmentRoleId: string;
|
environmentRoleId: string;
|
||||||
settings: Record<string, unknown>;
|
|
||||||
}[];
|
}[];
|
||||||
orgUsers: {
|
|
||||||
|
// Branches for both blocks and apps
|
||||||
|
subEnvironments: {
|
||||||
id: string;
|
id: string;
|
||||||
firstName: string;
|
envParentId: string;
|
||||||
lastName: string;
|
environmentRoleId: string;
|
||||||
email: string;
|
parentEnvironmentId: string;
|
||||||
provider: string;
|
subName: string;
|
||||||
orgRoleId: string;
|
|
||||||
uid: string;
|
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
envs: Record<
|
envs: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
variables: Record<string, { val: string }>;
|
variables: Record<
|
||||||
inherits: Record<string, unknown>;
|
string,
|
||||||
|
{
|
||||||
|
val?: string;
|
||||||
|
inheritsEnvironmentId?: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
inherits: Record<string, string[]>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user