Merge branch 'heads/main' into daniel/srp-removal-round-2

This commit is contained in:
Daniel Hougaard
2025-07-30 04:44:57 +04:00
474 changed files with 5372 additions and 28647 deletions
@@ -0,0 +1,96 @@
import { Knex } from "knex";
import { selectAllTableCols } from "@app/lib/knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment))) {
await knex.schema.createTable(TableName.AccessApprovalPolicyEnvironment, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment);
t.timestamps(true, true, true);
t.unique(["policyId", "envId"]);
});
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
.whereNotNull(`${TableName.AccessApprovalPolicy}.envId`);
const accessApprovalPolicies = existingAccessApprovalPolicies.map(async (policy) => {
await knex(TableName.AccessApprovalPolicyEnvironment).insert({
policyId: policy.id,
envId: policy.envId
});
});
await Promise.all(accessApprovalPolicies);
}
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment))) {
await knex.schema.createTable(TableName.SecretApprovalPolicyEnvironment, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE");
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment);
t.timestamps(true, true, true);
t.unique(["policyId", "envId"]);
});
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
.whereNotNull(`${TableName.SecretApprovalPolicy}.envId`);
const secretApprovalPolicies = existingSecretApprovalPolicies.map(async (policy) => {
await knex(TableName.SecretApprovalPolicyEnvironment).insert({
policyId: policy.id,
envId: policy.envId
});
});
await Promise.all(secretApprovalPolicies);
}
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
// Add the new foreign key constraint with ON DELETE SET NULL
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
});
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
// Add the new foreign key constraint with ON DELETE SET NULL
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
});
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment)) {
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyEnvironment);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
}
if (await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment)) {
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyEnvironment);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
}
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
});
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
});
}
@@ -0,0 +1,111 @@
/* eslint-disable no-await-in-loop */
import { Knex } from "knex";
import { chunkArray } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { TableName } from "../schemas";
import { TReminders, TRemindersInsert } from "../schemas/reminders";
export async function up(knex: Knex): Promise<void> {
logger.info("Initializing secret reminders migration");
const hasReminderTable = await knex.schema.hasTable(TableName.Reminder);
if (hasReminderTable) {
const secretsWithLatestVersions = await knex(TableName.SecretV2)
.whereNotNull(`${TableName.SecretV2}.reminderRepeatDays`)
.whereRaw(`"${TableName.SecretV2}"."reminderRepeatDays" > 0`)
.innerJoin(TableName.SecretVersionV2, (qb) => {
void qb
.on(`${TableName.SecretVersionV2}.secretId`, "=", `${TableName.SecretV2}.id`)
.andOn(`${TableName.SecretVersionV2}.reminderRepeatDays`, "=", `${TableName.SecretV2}.reminderRepeatDays`);
})
.whereIn([`${TableName.SecretVersionV2}.secretId`, `${TableName.SecretVersionV2}.version`], (qb) => {
void qb
.select(["v2.secretId", knex.raw("MIN(v2.version) as version")])
.from(`${TableName.SecretVersionV2} as v2`)
.innerJoin(`${TableName.SecretV2} as s2`, "v2.secretId", "s2.id")
.whereRaw(`v2."reminderRepeatDays" = s2."reminderRepeatDays"`)
.whereNotNull("v2.reminderRepeatDays")
.whereRaw(`v2."reminderRepeatDays" > 0`)
.groupBy("v2.secretId");
})
// Add LEFT JOIN with Reminder table to check for existing reminders
.leftJoin(TableName.Reminder, `${TableName.Reminder}.secretId`, `${TableName.SecretV2}.id`)
// Only include secrets that don't already have reminders
.whereNull(`${TableName.Reminder}.secretId`)
.select(
knex.ref("id").withSchema(TableName.SecretV2).as("secretId"),
knex.ref("reminderRepeatDays").withSchema(TableName.SecretV2).as("reminderRepeatDays"),
knex.ref("reminderNote").withSchema(TableName.SecretV2).as("reminderNote"),
knex.ref("createdAt").withSchema(TableName.SecretVersionV2).as("createdAt")
);
logger.info(`Found ${secretsWithLatestVersions.length} reminders to migrate`);
const reminderInserts: TRemindersInsert[] = [];
if (secretsWithLatestVersions.length > 0) {
secretsWithLatestVersions.forEach((secret) => {
if (!secret.reminderRepeatDays) return;
const now = new Date();
const createdAt = new Date(secret.createdAt);
let nextReminderDate = new Date(createdAt);
nextReminderDate.setDate(nextReminderDate.getDate() + secret.reminderRepeatDays);
// If the next reminder date is in the past, calculate the proper next occurrence
if (nextReminderDate < now) {
const daysSinceCreation = Math.floor((now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24));
const daysIntoCurrentCycle = daysSinceCreation % secret.reminderRepeatDays;
const daysUntilNextReminder = secret.reminderRepeatDays - daysIntoCurrentCycle;
nextReminderDate = new Date(now);
nextReminderDate.setDate(now.getDate() + daysUntilNextReminder);
}
reminderInserts.push({
secretId: secret.secretId,
message: secret.reminderNote,
repeatDays: secret.reminderRepeatDays,
nextReminderDate
});
});
const commitBatches = chunkArray(reminderInserts, 2000);
for (const commitBatch of commitBatches) {
const insertedReminders = (await knex
.batchInsert(TableName.Reminder, commitBatch)
.returning("*")) as TReminders[];
const insertedReminderSecretIds = insertedReminders.map((reminder) => reminder.secretId).filter(Boolean);
const recipients = await knex(TableName.SecretReminderRecipients)
.whereRaw(`??.?? IN (${insertedReminderSecretIds.map(() => "?").join(",")})`, [
TableName.SecretReminderRecipients,
"secretId",
...insertedReminderSecretIds
])
.select(
knex.ref("userId").withSchema(TableName.SecretReminderRecipients).as("userId"),
knex.ref("secretId").withSchema(TableName.SecretReminderRecipients).as("secretId")
);
const reminderRecipients = recipients.map((recipient) => ({
reminderId: insertedReminders.find((reminder) => reminder.secretId === recipient.secretId)?.id,
userId: recipient.userId
}));
const filteredRecipients = reminderRecipients.filter((recipient) => Boolean(recipient.reminderId));
await knex.batchInsert(TableName.ReminderRecipient, filteredRecipients);
}
logger.info(`Successfully migrated ${reminderInserts.length} secret reminders`);
}
logger.info("Secret reminders migration completed");
} else {
logger.warn("Reminder table does not exist, skipping migration");
}
}
export async function down(): Promise<void> {
logger.info("Rollback not implemented for secret reminders fix migration");
}
@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreValues"))) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.specificType("secretDetectionIgnoreValues", "text[]");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreValues")) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.dropColumn("secretDetectionIgnoreValues");
});
}
}
@@ -53,7 +53,7 @@ export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory
let envCfg = Object.freeze(parsedEnv.data);
const fipsEnabled = await crypto.initialize(superAdminDAL);
const fipsEnabled = await crypto.initialize(superAdminDAL, envCfg);
// Fix for 128-bit entropy encryption key expansion issue:
// In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY.
+2
View File
@@ -100,6 +100,7 @@ export enum TableName {
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
AccessApprovalRequest = "access_approval_requests",
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
AccessApprovalPolicyEnvironment = "access_approval_policies_environments",
SecretApprovalPolicy = "secret_approval_policies",
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
@@ -107,6 +108,7 @@ export enum TableName {
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags",
SecretApprovalPolicyEnvironment = "secret_approval_policies_environments",
SecretRotation = "secret_rotations",
SecretRotationOutput = "secret_rotation_outputs",
SamlConfig = "saml_configs",