misc: initial setup

This commit is contained in:
Sheen Capadngan
2025-05-13 05:02:15 +08:00
parent 531607dcb7
commit bc93db8603
13 changed files with 2280 additions and 47 deletions

View File

@@ -53,6 +53,7 @@ import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal-certificate-authority-service";
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
import { TExternalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
@@ -254,6 +255,7 @@ declare module "fastify" {
microsoftTeams: TMicrosoftTeamsServiceFactory;
assumePrivileges: TAssumePrivilegeServiceFactory;
githubOrgSync: TGithubOrgSyncServiceFactory;
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

View File

@@ -152,6 +152,9 @@ import {
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TInternalCertificateAuthorities,
TInternalCertificateAuthoritiesInsert,
TInternalCertificateAuthoritiesUpdate,
TInternalKms,
TInternalKmsInsert,
TInternalKmsUpdate,
@@ -530,6 +533,11 @@ declare module "knex/types/tables" {
TCertificateAuthorityCrlInsert,
TCertificateAuthorityCrlUpdate
>;
[TableName.InternalCertificateAuthority]: KnexOriginal.CompositeTableType<
TInternalCertificateAuthorities,
TInternalCertificateAuthoritiesInsert,
TInternalCertificateAuthoritiesUpdate
>;
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
[TableName.CertificateTemplate]: KnexOriginal.CompositeTableType<
TCertificateTemplates,

View File

@@ -0,0 +1,139 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasCATable = await knex.schema.hasTable(TableName.CertificateAuthority);
const hasExternalCATable = await knex.schema.hasTable(TableName.ExternalCertificateAuthority);
const hasInternalCATable = await knex.schema.hasTable(TableName.InternalCertificateAuthority);
if (hasCATable && !hasInternalCATable) {
await knex.schema.createTableLike(TableName.InternalCertificateAuthority, TableName.CertificateAuthority, (t) => {
t.uuid("certificateAuthorityId").nullable();
});
await knex(TableName.InternalCertificateAuthority).insert(knex(TableName.CertificateAuthority).select("*"));
await knex(TableName.InternalCertificateAuthority).update("certificateAuthorityId", knex.ref("id"));
await knex.schema.alterTable(TableName.InternalCertificateAuthority, (t) => {
t.dropColumn("projectId");
t.dropColumn("requireTemplateForIssuance");
t.dropColumn("createdAt");
t.dropColumn("updatedAt");
t.uuid("parentCaId")
.nullable()
.references("id")
.inTable(TableName.CertificateAuthority)
.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();
});
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
t.dropColumn("parentCaId");
t.dropColumn("type");
t.dropColumn("status");
t.dropColumn("friendlyName");
t.dropColumn("organization");
t.dropColumn("ou");
t.dropColumn("country");
t.dropColumn("province");
t.dropColumn("locality");
t.dropColumn("commonName");
t.dropColumn("dn");
t.dropColumn("serialNumber");
t.dropColumn("maxPathLength");
t.dropColumn("keyAlgorithm");
t.dropColumn("notBefore");
t.dropColumn("notAfter");
t.dropColumn("activeCaCertId");
t.renameColumn("requireTemplateForIssuance", "disableDirectIssuance");
});
}
if (!hasExternalCATable) {
await knex.schema.createTable(TableName.ExternalCertificateAuthority, (t) => {
//
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasCATable = await knex.schema.hasTable(TableName.CertificateAuthority);
const hasExternalCATable = await knex.schema.hasTable(TableName.ExternalCertificateAuthority);
const hasInternalCATable = await knex.schema.hasTable(TableName.InternalCertificateAuthority);
if (hasCATable && hasInternalCATable) {
// First add all columns as nullable
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
t.uuid("parentCaId").nullable().references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
t.string("type").nullable();
t.string("status").nullable();
t.string("friendlyName").nullable();
t.string("organization").nullable();
t.string("ou").nullable();
t.string("country").nullable();
t.string("province").nullable();
t.string("locality").nullable();
t.string("commonName").nullable();
t.string("dn").nullable();
t.string("serialNumber").nullable().unique();
t.integer("maxPathLength").nullable();
t.string("keyAlgorithm").nullable();
t.timestamp("notBefore").nullable();
t.timestamp("notAfter").nullable();
t.uuid("activeCaCertId").nullable().references("id").inTable(TableName.CertificateAuthorityCert);
t.renameColumn("disableDirectIssuance", "requireTemplateForIssuance");
});
await knex.raw(`
UPDATE ${TableName.CertificateAuthority} ca
SET
type = ica.type,
status = ica.status,
"friendlyName" = ica."friendlyName",
organization = ica.organization,
ou = ica.ou,
country = ica.country,
province = ica.province,
locality = ica.locality,
"commonName" = ica."commonName",
dn = ica.dn,
"parentCaId" = ica."parentCaId",
"serialNumber" = ica."serialNumber",
"maxPathLength" = ica."maxPathLength",
"keyAlgorithm" = ica."keyAlgorithm",
"notBefore" = ica."notBefore",
"notAfter" = ica."notAfter",
"activeCaCertId" = ica."activeCaCertId"
FROM ${TableName.InternalCertificateAuthority} ica
WHERE ca.id = ica.id
`);
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
t.string("type").notNullable().alter();
t.string("status").notNullable().alter();
t.string("friendlyName").notNullable().alter();
t.string("organization").notNullable().alter();
t.string("ou").notNullable().alter();
t.string("country").notNullable().alter();
t.string("province").notNullable().alter();
t.string("locality").notNullable().alter();
t.string("commonName").notNullable().alter();
t.string("dn").notNullable().alter();
t.string("keyAlgorithm").notNullable().alter();
});
await knex.schema.dropTable(TableName.InternalCertificateAuthority);
}
if (hasExternalCATable) {
await knex.schema.dropTable(TableName.ExternalCertificateAuthority);
}
}

View File

@@ -11,25 +11,8 @@ export const CertificateAuthoritiesSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
parentCaId: z.string().uuid().nullable().optional(),
projectId: z.string(),
type: z.string(),
status: z.string(),
friendlyName: z.string(),
organization: z.string(),
ou: z.string(),
country: z.string(),
province: z.string(),
locality: z.string(),
commonName: z.string(),
dn: z.string(),
serialNumber: z.string().nullable().optional(),
maxPathLength: z.number().nullable().optional(),
keyAlgorithm: z.string(),
notBefore: z.date().nullable().optional(),
notAfter: z.date().nullable().optional(),
activeCaCertId: z.string().uuid().nullable().optional(),
requireTemplateForIssuance: z.boolean().default(false)
disableDirectIssuance: z.boolean().default(false)
});
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;

View File

@@ -48,6 +48,7 @@ export * from "./identity-universal-auths";
export * from "./incident-contacts";
export * from "./integration-auths";
export * from "./integrations";
export * from "./internal-certificate-authorities";
export * from "./internal-kms";
export * from "./kmip-client-certificates";
export * from "./kmip-clients";

View File

@@ -0,0 +1,39 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const InternalCertificateAuthoritiesSchema = z.object({
id: z.string().uuid(),
parentCaId: z.string().uuid().nullable().optional(),
type: z.string(),
status: z.string(),
friendlyName: z.string(),
organization: z.string(),
ou: z.string(),
country: z.string(),
province: z.string(),
locality: z.string(),
commonName: z.string(),
dn: z.string(),
serialNumber: z.string().nullable().optional(),
maxPathLength: z.number().nullable().optional(),
keyAlgorithm: z.string(),
notBefore: z.date().nullable().optional(),
notAfter: z.date().nullable().optional(),
activeCaCertId: z.string().uuid().nullable().optional(),
certificateAuthorityId: z.string().uuid()
});
export type TInternalCertificateAuthorities = z.infer<typeof InternalCertificateAuthoritiesSchema>;
export type TInternalCertificateAuthoritiesInsert = Omit<
z.input<typeof InternalCertificateAuthoritiesSchema>,
TImmutableDBKeys
>;
export type TInternalCertificateAuthoritiesUpdate = Partial<
Omit<z.input<typeof InternalCertificateAuthoritiesSchema>, TImmutableDBKeys>
>;

View File

@@ -13,6 +13,8 @@ export enum TableName {
SshCertificate = "ssh_certificates",
SshCertificateBody = "ssh_certificate_bodies",
CertificateAuthority = "certificate_authorities",
ExternalCertificateAuthority = "external_certificate_authorities",
InternalCertificateAuthority = "internal_certificate_authorities",
CertificateTemplateEstConfig = "certificate_template_est_configs",
CertificateAuthorityCert = "certificate_authority_certs",
CertificateAuthoritySecret = "certificate_authority_secret",

View File

@@ -133,6 +133,8 @@ import { certificateAuthorityDALFactory } from "@app/services/certificate-author
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { internalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/internal-certificate-authority-dal";
import { internalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal-certificate-authority-service";
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
@@ -814,6 +816,7 @@ export const registerRoutes = async (
});
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
const internalCertificateAuthorityDAL = internalCertificateAuthorityDALFactory(db);
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
const certificateAuthoritySecretDAL = certificateAuthoritySecretDALFactory(db);
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
@@ -912,6 +915,24 @@ export const registerRoutes = async (
permissionService
});
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateTemplateDAL,
certificateAuthorityQueue,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
pkiCollectionDAL,
pkiCollectionItemDAL,
projectDAL,
internalCertificateAuthorityDAL,
kmsService,
permissionService
});
const certificateAuthorityCrlService = certificateAuthorityCrlServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCrlDAL,
@@ -1741,6 +1762,7 @@ export const registerRoutes = async (
sshHost: sshHostService,
sshHostGroup: sshHostGroupService,
certificateAuthority: certificateAuthorityService,
internalCertificateAuthority: internalCertificateAuthorityService,
certificateTemplate: certificateTemplateService,
certificateAuthorityCrl: certificateAuthorityCrlService,
certificateEst: certificateEstService,

View File

@@ -73,7 +73,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.createCa({
const ca = await server.services.internalCertificateAuthority.createCa({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -120,7 +120,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.getCaById({
const ca = await server.services.internalCertificateAuthority.getCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -167,7 +167,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req, res) => {
const caCert = await server.services.certificateAuthority.getCaCertById(req.params);
const caCert = await server.services.internalCertificateAuthority.getCaCertById(req.params);
res.header("Content-Type", "application/pkix-cert");
@@ -203,7 +203,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.updateCaById({
const ca = await server.services.internalCertificateAuthority.updateCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -252,7 +252,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.deleteCaById({
const ca = await server.services.internalCertificateAuthority.deleteCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -299,7 +299,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { ca, csr } = await server.services.certificateAuthority.getCaCsr({
const { ca, csr } = await server.services.internalCertificateAuthority.getCaCsr({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -353,7 +353,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, serialNumber, ca } =
await server.services.certificateAuthority.renewCaCert({
await server.services.internalCertificateAuthority.renewCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -408,7 +408,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { caCerts, ca } = await server.services.certificateAuthority.getCaCerts({
const { caCerts, ca } = await server.services.internalCertificateAuthority.getCaCerts({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -455,13 +455,14 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificate, certificateChain, serialNumber, ca } = await server.services.certificateAuthority.getCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
const { certificate, certificateChain, serialNumber, ca } =
await server.services.internalCertificateAuthority.getCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -517,7 +518,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca } =
await server.services.certificateAuthority.signIntermediate({
await server.services.internalCertificateAuthority.signIntermediate({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -574,7 +575,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { ca } = await server.services.certificateAuthority.importCertToCa({
const { ca } = await server.services.internalCertificateAuthority.importCertToCa({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -653,7 +654,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
await server.services.certificateAuthority.issueCertFromCa({
await server.services.internalCertificateAuthority.issueCertFromCa({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -746,7 +747,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca, commonName } =
await server.services.certificateAuthority.signCertFromCa({
await server.services.internalCertificateAuthority.signCertFromCa({
isInternal: false,
caId: req.params.caId,
actor: req.permission.type,
@@ -809,13 +810,15 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificateTemplates, ca } = await server.services.certificateAuthority.getCaCertificateTemplates({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
const { certificateTemplates, ca } = await server.services.internalCertificateAuthority.getCaCertificateTemplates(
{
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
}
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,

View File

@@ -1,13 +1,80 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { CertificateAuthoritiesSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TCertificateAuthorityDALFactory = ReturnType<typeof certificateAuthorityDALFactory>;
export const certificateAuthorityDALFactory = (db: TDbClient) => {
const caOrm = ormify(db, TableName.CertificateAuthority);
const findByIdWithAssociatedCa = async (caId: string, tx?: Knex) => {
const result = await (tx || db.replicaNode())(TableName.CertificateAuthority)
.leftJoin(
TableName.InternalCertificateAuthority,
`${TableName.CertificateAuthority}.id`,
`${TableName.InternalCertificateAuthority}.certificateAuthorityId`
)
.where(`${TableName.CertificateAuthority}.id`, caId)
.select(selectAllTableCols(TableName.CertificateAuthority))
.select(
db.ref("id").withSchema(TableName.InternalCertificateAuthority).as("internalCaId"),
db.ref("parentCaId").withSchema(TableName.InternalCertificateAuthority).as("internalParentCaId"),
db.ref("type").withSchema(TableName.InternalCertificateAuthority).as("internalType"),
db.ref("status").withSchema(TableName.InternalCertificateAuthority).as("internalStatus"),
db.ref("friendlyName").withSchema(TableName.InternalCertificateAuthority).as("internalFriendlyName"),
db.ref("organization").withSchema(TableName.InternalCertificateAuthority).as("internalOrganization"),
db.ref("ou").withSchema(TableName.InternalCertificateAuthority).as("internalOu"),
db.ref("country").withSchema(TableName.InternalCertificateAuthority).as("internalCountry"),
db.ref("province").withSchema(TableName.InternalCertificateAuthority).as("internalProvince"),
db.ref("locality").withSchema(TableName.InternalCertificateAuthority).as("internalLocality"),
db.ref("commonName").withSchema(TableName.InternalCertificateAuthority).as("internalCommonName"),
db.ref("dn").withSchema(TableName.InternalCertificateAuthority).as("internalDn"),
db.ref("serialNumber").withSchema(TableName.InternalCertificateAuthority).as("internalSerialNumber"),
db.ref("maxPathLength").withSchema(TableName.InternalCertificateAuthority).as("internalMaxPathLength"),
db.ref("keyAlgorithm").withSchema(TableName.InternalCertificateAuthority).as("internalKeyAlgorithm"),
db.ref("notBefore").withSchema(TableName.InternalCertificateAuthority).as("internalNotBefore"),
db.ref("notAfter").withSchema(TableName.InternalCertificateAuthority).as("internalNotAfter"),
db.ref("activeCaCertId").withSchema(TableName.InternalCertificateAuthority).as("internalActiveCaCertId"),
db
.ref("certificateAuthorityId")
.withSchema(TableName.InternalCertificateAuthority)
.as("internalCertificateAuthorityId")
)
.first();
const data = {
...CertificateAuthoritiesSchema.parse(result),
internalCa: result
? {
id: result.internalCaId,
parentCaId: result.internalParentCaId,
type: result.internalType,
status: result.internalStatus,
friendlyName: result.internalFriendlyName,
organization: result.internalOrganization,
ou: result.internalOu,
country: result.internalCountry,
province: result.internalProvince,
locality: result.internalLocality,
commonName: result.internalCommonName,
dn: result.internalDn,
serialNumber: result.internalSerialNumber,
maxPathLength: result.internalMaxPathLength,
keyAlgorithm: result.internalKeyAlgorithm,
notBefore: result.internalNotBefore,
notAfter: result.internalNotAfter,
activeCaCertId: result.internalActiveCaCertId,
certificateAuthorityId: result.internalCertificateAuthorityId
}
: undefined
};
return data;
};
// note: not used
const buildCertificateChain = async (caId: string) => {
try {
@@ -44,6 +111,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
return {
...caOrm,
buildCertificateChain
buildCertificateChain,
findByIdWithAssociatedCa
};
};

View File

@@ -5,6 +5,7 @@ import { NotFoundError } from "@app/lib/errors";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import {
TDNParts,
TGetCaCertChainDTO,
@@ -318,3 +319,10 @@ export const rebuildCaCrl = async ({
}
);
};
export const expandInternalCa = (
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>
) => ({
...ca,
...ca.internalCa
});

View File

@@ -0,0 +1,13 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TInternalCertificateAuthorityDALFactory = ReturnType<typeof internalCertificateAuthorityDALFactory>;
export const internalCertificateAuthorityDALFactory = (db: TDbClient) => {
const caOrm = ormify(db, TableName.InternalCertificateAuthority);
return {
...caOrm
};
};