mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 06:35:21 +00:00
116 lines
4.7 KiB
TypeScript
116 lines
4.7 KiB
TypeScript
import { Knex } from "knex";
|
|
|
|
import { inMemoryKeyStore } from "@app/keystore/memory";
|
|
import { selectAllTableCols } from "@app/lib/knex";
|
|
import { initLogger } from "@app/lib/logger";
|
|
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
|
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
|
|
|
import { TableName } from "../schemas";
|
|
import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
|
|
import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
|
|
|
|
// Note(daniel): We aren't dropping tables or columns in this migrations so we can easily rollback if needed.
|
|
// In the future we need to drop the projectGatewayId on the dynamic secrets table, and drop the project_gateways table entirely.
|
|
|
|
const BATCH_SIZE = 500;
|
|
|
|
export async function up(knex: Knex): Promise<void> {
|
|
// eslint-disable-next-line no-param-reassign
|
|
knex.replicaNode = () => {
|
|
return knex;
|
|
};
|
|
|
|
if (!(await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId"))) {
|
|
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
|
|
table.uuid("gatewayId").nullable();
|
|
table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL");
|
|
|
|
table.index("gatewayId");
|
|
});
|
|
|
|
const existingDynamicSecretsWithProjectGatewayId = await knex(TableName.DynamicSecret)
|
|
.select(selectAllTableCols(TableName.DynamicSecret))
|
|
.whereNotNull(`${TableName.DynamicSecret}.projectGatewayId`)
|
|
.join(TableName.ProjectGateway, `${TableName.ProjectGateway}.id`, `${TableName.DynamicSecret}.projectGatewayId`)
|
|
.whereNotNull(`${TableName.ProjectGateway}.gatewayId`)
|
|
.select(
|
|
knex.ref("projectId").withSchema(TableName.ProjectGateway).as("projectId"),
|
|
knex.ref("gatewayId").withSchema(TableName.ProjectGateway).as("projectGatewayGatewayId")
|
|
);
|
|
|
|
initLogger();
|
|
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
|
|
const superAdminDAL = superAdminDALFactory(knex);
|
|
const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
|
|
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
|
|
const keyStore = inMemoryKeyStore();
|
|
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
|
|
|
const updatedDynamicSecrets = await Promise.all(
|
|
existingDynamicSecretsWithProjectGatewayId.map(async (existingDynamicSecret) => {
|
|
if (!existingDynamicSecret.projectGatewayGatewayId) {
|
|
const result = {
|
|
...existingDynamicSecret,
|
|
gatewayId: null
|
|
};
|
|
|
|
const { projectId, projectGatewayGatewayId, ...rest } = result;
|
|
return rest;
|
|
}
|
|
|
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.SecretManager,
|
|
projectId: existingDynamicSecret.projectId
|
|
});
|
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.SecretManager,
|
|
projectId: existingDynamicSecret.projectId
|
|
});
|
|
|
|
let decryptedStoredInput = JSON.parse(
|
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(existingDynamicSecret.encryptedInput) }).toString()
|
|
) as object;
|
|
|
|
// We're not removing the existing projectGatewayId from the input so we can easily rollback without having to re-encrypt the input
|
|
decryptedStoredInput = {
|
|
...decryptedStoredInput,
|
|
gatewayId: existingDynamicSecret.projectGatewayGatewayId
|
|
};
|
|
|
|
const encryptedInput = secretManagerEncryptor({
|
|
plainText: Buffer.from(JSON.stringify(decryptedStoredInput))
|
|
}).cipherTextBlob;
|
|
|
|
const result = {
|
|
...existingDynamicSecret,
|
|
encryptedInput,
|
|
gatewayId: existingDynamicSecret.projectGatewayGatewayId
|
|
};
|
|
|
|
const { projectId, projectGatewayGatewayId, ...rest } = result;
|
|
return rest;
|
|
})
|
|
);
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function down(knex: Knex): Promise<void> {
|
|
// no re-encryption needed as we keep the old projectGatewayId in the input
|
|
if (await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId")) {
|
|
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
|
|
table.dropForeign("gatewayId");
|
|
table.dropColumn("gatewayId");
|
|
});
|
|
}
|
|
}
|