diff --git a/backend/src/db/migrations/20250512133213_add-external-ca-pki.ts b/backend/src/db/migrations/20250521110635_add-external-ca-pki.ts similarity index 75% rename from backend/src/db/migrations/20250512133213_add-external-ca-pki.ts rename to backend/src/db/migrations/20250521110635_add-external-ca-pki.ts index 9ac60e5a0..34d8d2052 100644 --- a/backend/src/db/migrations/20250512133213_add-external-ca-pki.ts +++ b/backend/src/db/migrations/20250521110635_add-external-ca-pki.ts @@ -1,5 +1,8 @@ +import slugify from "@sindresorhus/slugify"; import { Knex } from "knex"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; + import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { @@ -9,12 +12,12 @@ export async function up(knex: Knex): Promise { if (hasCATable && !hasInternalCATable) { await knex.schema.createTableLike(TableName.InternalCertificateAuthority, TableName.CertificateAuthority, (t) => { - t.uuid("certificateAuthorityId").nullable(); + t.uuid("caId").nullable(); }); // @ts-expect-error intentional: migration await knex(TableName.InternalCertificateAuthority).insert(knex(TableName.CertificateAuthority).select("*")); - await knex(TableName.InternalCertificateAuthority).update("certificateAuthorityId", knex.ref("id")); + await knex(TableName.InternalCertificateAuthority).update("caId", knex.ref("id")); await knex.schema.alterTable(TableName.InternalCertificateAuthority, (t) => { t.dropColumn("projectId"); @@ -28,14 +31,31 @@ export async function up(knex: Knex): Promise { .onDelete("CASCADE") .alter(); t.uuid("activeCaCertId").nullable().references("id").inTable(TableName.CertificateAuthorityCert).alter(); - t.uuid("certificateAuthorityId") - .notNullable() - .references("id") - .inTable(TableName.CertificateAuthority) - .onDelete("CASCADE") - .alter(); + t.uuid("caId").notNullable().references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE").alter(); }); + await knex.schema.alterTable(TableName.CertificateAuthority, (t) => { + t.renameColumn("requireTemplateForIssuance", "enableDirectIssuance"); + t.string("name").nullable(); + }); + + // prefill name for existing internal CAs and flip enableDirectIssuance + const cas = await knex(TableName.CertificateAuthority).select("id", "friendlyName", "enableDirectIssuance"); + await Promise.all( + cas.map((ca) => { + const slugifiedName = ca.friendlyName + ? slugify(`${ca.friendlyName}-${alphaNumericNanoId(8)}`) + : slugify(alphaNumericNanoId(12)); + + return ( + knex(TableName.CertificateAuthority) + .where({ id: ca.id }) + // @ts-expect-error intentional: migration + .update({ name: slugifiedName, enableDirectIssuance: !ca.enableDirectIssuance }) + ); + }) + ); + await knex.schema.alterTable(TableName.CertificateAuthority, (t) => { t.dropColumn("parentCaId"); t.dropColumn("type"); @@ -54,7 +74,8 @@ export async function up(knex: Knex): Promise { t.dropColumn("notBefore"); t.dropColumn("notAfter"); t.dropColumn("activeCaCertId"); - t.renameColumn("requireTemplateForIssuance", "disableDirectIssuance"); + t.string("name").notNullable().alter(); + t.unique(["name", "projectId"]); }); } @@ -62,22 +83,14 @@ export async function up(knex: Knex): Promise { await knex.schema.createTable(TableName.ExternalCertificateAuthority, (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("type").notNullable(); - t.string("name").notNullable(); - t.string("projectId").notNullable(); - t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.uuid("appConnectionId").nullable(); t.foreign("appConnectionId").references("id").inTable(TableName.AppConnection); t.uuid("dnsAppConnectionId").nullable(); t.foreign("dnsAppConnectionId").references("id").inTable(TableName.AppConnection); - t.uuid("certificateAuthorityId") - .notNullable() - .references("id") - .inTable(TableName.CertificateAuthority) - .onDelete("CASCADE"); + t.uuid("caId").notNullable().references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE"); t.binary("credentials"); t.json("configuration"); t.string("status").notNullable(); - t.unique(["projectId", "name"]); }); } @@ -116,9 +129,23 @@ export async function down(knex: Knex): Promise { t.timestamp("notBefore").nullable(); t.timestamp("notAfter").nullable(); t.uuid("activeCaCertId").nullable().references("id").inTable(TableName.CertificateAuthorityCert); - t.renameColumn("disableDirectIssuance", "requireTemplateForIssuance"); + t.renameColumn("enableDirectIssuance", "requireTemplateForIssuance"); + t.dropColumn("name"); }); + // flip requireTemplateForIssuance for existing internal CAs + const cas = await knex(TableName.CertificateAuthority).select("id", "requireTemplateForIssuance"); + await Promise.all( + cas.map((ca) => { + return ( + knex(TableName.CertificateAuthority) + .where({ id: ca.id }) + // @ts-expect-error intentional: migration + .update({ requireTemplateForIssuance: !ca.requireTemplateForIssuance }) + ); + }) + ); + await knex.raw(` UPDATE ${TableName.CertificateAuthority} ca SET @@ -140,7 +167,7 @@ export async function down(knex: Knex): Promise { "notAfter" = ica."notAfter", "activeCaCertId" = ica."activeCaCertId" FROM ${TableName.InternalCertificateAuthority} ica - WHERE ca.id = ica."certificateAuthorityId" + WHERE ca.id = ica."caId" `); await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {