mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 13:23:23 +00:00
112 lines
5.0 KiB
TypeScript
112 lines
5.0 KiB
TypeScript
import { Knex } from "knex";
|
|
|
|
import { inMemoryKeyStore } from "@app/keystore/memory";
|
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
|
import { selectAllTableCols } from "@app/lib/knex";
|
|
import { initLogger } from "@app/lib/logger";
|
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
|
|
|
import { SecretKeyEncoding, TableName } from "../schemas";
|
|
import { getMigrationEnvConfig } from "./utils/env-config";
|
|
import { createCircularCache } from "./utils/ring-buffer";
|
|
import { getMigrationEncryptionServices } from "./utils/services";
|
|
|
|
const BATCH_SIZE = 500;
|
|
export async function up(knex: Knex): Promise<void> {
|
|
const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput");
|
|
const hasInputCiphertextColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext");
|
|
const hasInputIVColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV");
|
|
const hasInputTagColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag");
|
|
|
|
const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret);
|
|
if (hasDynamicSecretTable) {
|
|
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
|
|
if (!hasEncryptedInputColumn) t.binary("encryptedInput");
|
|
if (hasInputCiphertextColumn) t.text("inputCiphertext").nullable().alter();
|
|
if (hasInputIVColumn) t.string("inputIV").nullable().alter();
|
|
if (hasInputTagColumn) t.string("inputTag").nullable().alter();
|
|
});
|
|
}
|
|
|
|
initLogger();
|
|
const envConfig = getMigrationEnvConfig();
|
|
const keyStore = inMemoryKeyStore();
|
|
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
|
const projectEncryptionRingBuffer =
|
|
createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25);
|
|
|
|
const dynamicSecretRootCredentials = await knex(TableName.DynamicSecret)
|
|
.join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`)
|
|
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
|
.select(selectAllTableCols(TableName.DynamicSecret))
|
|
.select(knex.ref("projectId").withSchema(TableName.Environment))
|
|
.orderBy(`${TableName.Environment}.projectId` as "projectId");
|
|
|
|
const updatedDynamicSecrets = await Promise.all(
|
|
dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => {
|
|
let projectKmsService = projectEncryptionRingBuffer.getItem(projectId);
|
|
if (!projectKmsService) {
|
|
projectKmsService = await kmsService.createCipherPairWithDataKey(
|
|
{
|
|
type: KmsDataKey.SecretManager,
|
|
projectId
|
|
},
|
|
knex
|
|
);
|
|
projectEncryptionRingBuffer.push(projectId, projectKmsService);
|
|
}
|
|
|
|
const decryptedInputData =
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
|
el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding
|
|
? infisicalSymmetricDecrypt({
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
|
keyEncoding: el.keyEncoding as SecretKeyEncoding,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
|
iv: el.inputIV,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
|
tag: el.inputTag,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
|
ciphertext: el.inputCiphertext
|
|
})
|
|
: "";
|
|
|
|
const encryptedInput = projectKmsService.encryptor({
|
|
plainText: Buffer.from(decryptedInputData)
|
|
}).cipherTextBlob;
|
|
|
|
return { ...el, encryptedInput };
|
|
})
|
|
);
|
|
|
|
for (let i = 0; i < updatedDynamicSecrets.length; i += BATCH_SIZE) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await knex(TableName.DynamicSecret)
|
|
.insert(updatedDynamicSecrets.slice(i, i + BATCH_SIZE))
|
|
.onConflict("id")
|
|
.merge();
|
|
}
|
|
|
|
if (hasDynamicSecretTable) {
|
|
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
|
|
if (!hasEncryptedInputColumn) t.binary("encryptedInput").notNullable().alter();
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function down(knex: Knex): Promise<void> {
|
|
const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput");
|
|
|
|
const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret);
|
|
if (hasDynamicSecretTable) {
|
|
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
|
|
if (hasEncryptedInputColumn) t.dropColumn("encryptedInput");
|
|
});
|
|
}
|
|
}
|