misc: added internal CA route

This commit is contained in:
Sheen Capadngan
2025-05-14 23:10:10 +08:00
parent d1e5ae2d85
commit 6faad102e2
26 changed files with 1063 additions and 264 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service"; import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service"; import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal-certificate-authority-service"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { TCmekServiceFactory } from "@app/services/cmek/cmek-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"; import { TExternalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
+8
View File
@@ -68,6 +68,9 @@ import {
TDynamicSecrets, TDynamicSecrets,
TDynamicSecretsInsert, TDynamicSecretsInsert,
TDynamicSecretsUpdate, TDynamicSecretsUpdate,
TExternalCertificateAuthorities,
TExternalCertificateAuthoritiesInsert,
TExternalCertificateAuthoritiesUpdate,
TExternalGroupOrgRoleMappings, TExternalGroupOrgRoleMappings,
TExternalGroupOrgRoleMappingsInsert, TExternalGroupOrgRoleMappingsInsert,
TExternalGroupOrgRoleMappingsUpdate, TExternalGroupOrgRoleMappingsUpdate,
@@ -543,6 +546,11 @@ declare module "knex/types/tables" {
TInternalCertificateAuthoritiesInsert, TInternalCertificateAuthoritiesInsert,
TInternalCertificateAuthoritiesUpdate TInternalCertificateAuthoritiesUpdate
>; >;
[TableName.ExternalCertificateAuthority]: KnexOriginal.CompositeTableType<
TExternalCertificateAuthorities,
TExternalCertificateAuthoritiesInsert,
TExternalCertificateAuthoritiesUpdate
>;
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>; [TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
[TableName.CertificateTemplate]: KnexOriginal.CompositeTableType< [TableName.CertificateTemplate]: KnexOriginal.CompositeTableType<
TCertificateTemplates, TCertificateTemplates,
@@ -12,7 +12,12 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("certificateAuthorityId").nullable(); t.uuid("certificateAuthorityId").nullable();
}); });
await knex(TableName.InternalCertificateAuthority).insert(knex(TableName.CertificateAuthority).select("*")); const caRows = await knex(TableName.CertificateAuthority).select("*");
if (caRows.length > 0) {
// @ts-expect-error intentional: migration
await knex(TableName.InternalCertificateAuthority).insert(caRows);
}
await knex(TableName.InternalCertificateAuthority).update("certificateAuthorityId", knex.ref("id")); await knex(TableName.InternalCertificateAuthority).update("certificateAuthorityId", knex.ref("id"));
await knex.schema.alterTable(TableName.InternalCertificateAuthority, (t) => { await knex.schema.alterTable(TableName.InternalCertificateAuthority, (t) => {
@@ -59,7 +64,20 @@ export async function up(knex: Knex): Promise<void> {
if (!hasExternalCATable) { if (!hasExternalCATable) {
await knex.schema.createTable(TableName.ExternalCertificateAuthority, (t) => { 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.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.binary("credentials");
t.json("configuration");
}); });
} }
} }
@@ -113,7 +131,7 @@ export async function down(knex: Knex): Promise<void> {
"notAfter" = ica."notAfter", "notAfter" = ica."notAfter",
"activeCaCertId" = ica."activeCaCertId" "activeCaCertId" = ica."activeCaCertId"
FROM ${TableName.InternalCertificateAuthority} ica FROM ${TableName.InternalCertificateAuthority} ica
WHERE ca.id = ica.id WHERE ca.id = ica."certificateAuthorityId"
`); `);
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => { await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
@@ -0,0 +1,30 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const ExternalCertificateAuthoritiesSchema = z.object({
id: z.string().uuid(),
type: z.string(),
name: z.string(),
appConnectionId: z.string().uuid().nullable().optional(),
dnsAppConnectionId: z.string().uuid().nullable().optional(),
certificateAuthorityId: z.string().uuid(),
credentials: zodBuffer.nullable().optional(),
configuration: z.unknown().nullable().optional()
});
export type TExternalCertificateAuthorities = z.infer<typeof ExternalCertificateAuthoritiesSchema>;
export type TExternalCertificateAuthoritiesInsert = Omit<
z.input<typeof ExternalCertificateAuthoritiesSchema>,
TImmutableDBKeys
>;
export type TExternalCertificateAuthoritiesUpdate = Partial<
Omit<z.input<typeof ExternalCertificateAuthoritiesSchema>, TImmutableDBKeys>
>;
+1
View File
@@ -20,6 +20,7 @@ export * from "./certificate-templates";
export * from "./certificates"; export * from "./certificates";
export * from "./dynamic-secret-leases"; export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets"; export * from "./dynamic-secrets";
export * from "./external-certificate-authorities";
export * from "./external-group-org-role-mappings"; export * from "./external-group-org-role-mappings";
export * from "./external-kms"; export * from "./external-kms";
export * from "./gateways"; export * from "./gateways";
@@ -20,7 +20,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types"; import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types"; import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types"; import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
@@ -6,7 +6,7 @@ import { isCertChainValid } from "@app/services/certificate/certificate-fns";
import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { getCaCertChain, getCaCertChains } from "@app/services/certificate-authority/certificate-authority-fns"; import { getCaCertChain, getCaCertChains } from "@app/services/certificate-authority/certificate-authority-fns";
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal-certificate-authority-service"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
import { TCertificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { TCertificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
+2 -2
View File
@@ -133,8 +133,8 @@ import { certificateAuthorityDALFactory } from "@app/services/certificate-author
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue"; import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal"; import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service"; import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { internalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/internal-certificate-authority-dal"; import { internalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-dal";
import { internalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal-certificate-authority-service"; import { internalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal"; import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
@@ -10,7 +10,11 @@ import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types"; import {
CaRenewalType,
CaStatus,
InternalCaType
} from "@app/services/certificate-authority/certificate-authority-enums";
import { import {
validateAltNamesField, validateAltNamesField,
validateCaDateField validateCaDateField
@@ -34,7 +38,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
body: z body: z
.object({ .object({
projectSlug: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.projectSlug), projectSlug: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.projectSlug),
type: z.nativeEnum(CaType).describe(CERTIFICATE_AUTHORITIES.CREATE.type), type: z.nativeEnum(InternalCaType).describe(CERTIFICATE_AUTHORITIES.CREATE.type),
friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.CREATE.friendlyName), friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.CREATE.friendlyName),
commonName: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.commonName), commonName: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.commonName),
organization: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.organization), organization: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.organization),
@@ -79,6 +83,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
isInternal: false,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.body ...req.body
}); });
@@ -209,6 +214,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
caId: req.params.caId, caId: req.params.caId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
isInternal: false,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.body ...req.body
@@ -0,0 +1,246 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import {
TCertificateAuthority,
TCertificateAuthorityInput
} from "@app/services/certificate-authority/certificate-authority-types";
export const registerCertificateAuthorityEndpoints = <
T extends TCertificateAuthority,
I extends TCertificateAuthorityInput
>({
server,
caType,
createSchema,
updateSchema,
responseSchema
}: {
caType: CaType;
server: FastifyZodProvider;
createSchema: z.ZodType<{
name: string;
projectId: string;
configuration: I["configuration"];
disableDirectIssuance: boolean;
}>;
updateSchema: z.ZodType<{
name?: string;
configuration?: I["configuration"];
disableDirectIssuance?: boolean;
}>;
responseSchema: z.ZodTypeAny;
}) => {
server.route({
method: "GET",
url: `/`,
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
querystring: z.object({
projectId: z.string().trim().min(1, "Project ID required")
}),
response: {
200: z.object({ certificateAuthorities: responseSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId }
} = req;
const certificateAuthorities = (await server.services.certificateAuthority.listCertificateAuthoritiesByProjectId(
{ projectId, type: caType },
req.permission
)) as T[];
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId,
// event: {
// type: EventType.GET_SECRET_SYNCS,
// metadata: {
// destination,
// count: secretSyncs.length,
// syncIds: secretSyncs.map((connection) => connection.id)
// }
// }
// });
return { certificateAuthorities };
}
});
server.route({
method: "GET",
url: "/:certificateAuthorityId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
certificateAuthorityId: z.string().uuid()
}),
response: {
200: z.object({ certificateAuthority: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateAuthorityId } = req.params;
const certificateAuthority = (await server.services.certificateAuthority.findCertificateAuthorityById(
{ certificateAuthorityId, type: caType },
req.permission
)) as T;
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: secretSync.projectId,
// event: {
// type: EventType.GET_SECRET_SYNC,
// metadata: {
// syncId,
// destination
// }
// }
// });
return { certificateAuthority };
}
});
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
body: createSchema,
response: {
200: z.object({ certificateAuthority: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateAuthority = (await server.services.certificateAuthority.createCertificateAuthority(
{ ...req.body, type: caType },
req.permission
)) as T;
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: secretSync.projectId,
// event: {
// type: EventType.CREATE_SECRET_SYNC,
// metadata: {
// syncId: secretSync.id,
// destination,
// ...req.body
// }
// }
// });
return { certificateAuthority };
}
});
server.route({
method: "PATCH",
url: "/:certificateAuthorityId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
certificateAuthorityId: z.string().uuid()
}),
body: updateSchema,
response: {
200: z.object({ certificateAuthority: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateAuthorityId } = req.params;
const certificateAuthority = (await server.services.certificateAuthority.updateCertificateAuthority(
{ ...req.body, id: certificateAuthorityId, type: caType },
req.permission
)) as T;
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: certificateAuthority.projectId,
// event: {
// type: EventType.UPDATE_SECRET_SYNC,
// metadata: {
// syncId,
// destination,
// ...req.body
// }
// }
// });
return { certificateAuthority };
}
});
server.route({
method: "DELETE",
url: `/:certificateAuthorityId`,
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
certificateAuthorityId: z.string().uuid()
}),
response: {
200: z.object({ certificateAuthority: responseSchema })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateAuthorityId } = req.params;
const certificateAuthority = (await server.services.certificateAuthority.deleteCertificateAuthority(
{ id: certificateAuthorityId, type: caType },
req.permission
)) as T;
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// orgId: req.permission.orgId,
// event: {
// type: EventType.DELETE_SECRET_SYNC,
// metadata: {
// destination,
// syncId,
// removeSecrets
// }
// }
// });
return { certificateAuthority };
}
});
};
@@ -0,0 +1,11 @@
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import { registerInternalCertificateAuthorityRouter } from "./internal-certificate-authority-router";
export * from "./internal-certificate-authority-router";
export const CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP: Record<CaType, (server: FastifyZodProvider) => Promise<void>> =
{
[CaType.INTERNAL]: registerInternalCertificateAuthorityRouter,
[CaType.ACME]: registerInternalCertificateAuthorityRouter
};
@@ -0,0 +1,18 @@
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import {
CreateInternalCertificateAuthoritySchema,
InternalCertificateAuthoritySchema,
UpdateInternalCertificateAuthoritySchema
} from "@app/services/certificate-authority/internal/internal-certificate-authority-schemas";
import { registerCertificateAuthorityEndpoints } from "./certificate-authority-endpoints";
export const registerInternalCertificateAuthorityRouter = async (server: FastifyZodProvider) => {
registerCertificateAuthorityEndpoints({
caType: CaType.INTERNAL,
server,
responseSchema: InternalCertificateAuthoritySchema,
createSchema: CreateInternalCertificateAuthoritySchema,
updateSchema: UpdateInternalCertificateAuthoritySchema
});
};
+11
View File
@@ -10,6 +10,7 @@ import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router"; import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router"; import { registerProjectBotRouter } from "./bot-router";
import { registerCaRouter } from "./certificate-authority-router"; import { registerCaRouter } from "./certificate-authority-router";
import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers";
import { registerCertRouter } from "./certificate-router"; import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router"; import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router"; import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
@@ -102,6 +103,16 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register( await server.register(
async (pkiRouter) => { async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" }); await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
await pkiRouter.register(
async (caRouter) => {
for await (const [caType, router] of Object.entries(CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP)) {
await caRouter.register(router, { prefix: `/${caType}` });
}
},
{
prefix: "/ca"
}
);
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" }); await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" }); await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" }); await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
@@ -21,7 +21,7 @@ import { slugSchema } from "@app/server/lib/schemas";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums";
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema"; import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema"; import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema";
import { ProjectFilterType } from "@app/services/project/project-types"; import { ProjectFilterType } from "@app/services/project/project-types";
@@ -125,6 +125,11 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
`${TableName.CertificateAuthority}.id`, `${TableName.CertificateAuthority}.id`,
`${TableName.InternalCertificateAuthority}.certificateAuthorityId` `${TableName.InternalCertificateAuthority}.certificateAuthorityId`
) )
.leftJoin(
TableName.ExternalCertificateAuthority,
`${TableName.CertificateAuthority}.id`,
`${TableName.ExternalCertificateAuthority}.certificateAuthorityId`
)
.where(filter) .where(filter)
.select(selectAllTableCols(TableName.CertificateAuthority)) .select(selectAllTableCols(TableName.CertificateAuthority))
.select( .select(
@@ -150,6 +155,14 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
.ref("certificateAuthorityId") .ref("certificateAuthorityId")
.withSchema(TableName.InternalCertificateAuthority) .withSchema(TableName.InternalCertificateAuthority)
.as("internalCertificateAuthorityId") .as("internalCertificateAuthorityId")
)
.select(
db.ref("id").withSchema(TableName.ExternalCertificateAuthority).as("externalCaId"),
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
db
.ref("certificateAuthorityId")
.withSchema(TableName.ExternalCertificateAuthority)
.as("externalCertificateAuthorityId")
); );
if (limit) void query.limit(limit); if (limit) void query.limit(limit);
@@ -0,0 +1,19 @@
export enum CaType {
INTERNAL = "internal",
ACME = "acme"
}
export enum InternalCaType {
ROOT = "root",
INTERMEDIATE = "intermediate"
}
export enum CaStatus {
ACTIVE = "active",
DISABLED = "disabled",
PENDING_CERTIFICATE = "pending-certificate"
}
export enum CaRenewalType {
EXISTING = "existing"
}
@@ -12,7 +12,7 @@ import {
TGetCaCertChainsDTO, TGetCaCertChainsDTO,
TGetCaCredentialsDTO, TGetCaCredentialsDTO,
TRebuildCaCrlDTO TRebuildCaCrlDTO
} from "./certificate-authority-types"; } from "./internal/internal-certificate-authority-types";
/* eslint-disable no-bitwise */ /* eslint-disable no-bitwise */
export const createSerialNumber = () => { export const createSerialNumber = () => {
@@ -16,7 +16,7 @@ import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificat
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { keyAlgorithmToAlgCfg } from "./certificate-authority-fns"; import { keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
import { TRotateCaCrlTriggerDTO } from "./certificate-authority-types"; import { TRotateCaCrlTriggerDTO } from "./internal/internal-certificate-authority-types";
type TCertificateAuthorityQueueFactoryDep = { type TCertificateAuthorityQueueFactoryDep = {
// TODO: Pick // TODO: Pick
@@ -0,0 +1,29 @@
import z from "zod";
import { CertificateAuthoritiesSchema } from "@app/db/schemas";
import { slugSchema } from "@app/server/lib/schemas";
import { CaType } from "./certificate-authority-enums";
// SHEEN TODO: add description mapping using type
export const BaseCertificateAuthoritySchema = (type: CaType) =>
CertificateAuthoritiesSchema.pick({
projectId: true,
disableDirectIssuance: true,
id: true
}).extend({
name: z.string()
});
export const GenericCreateCertificateAuthorityFieldsSchema = (type: CaType) =>
z.object({
name: slugSchema({ field: "name" }),
projectId: z.string().trim().min(1, "Project ID required"),
disableDirectIssuance: z.boolean()
});
export const GenericUpdateCertificateAuthorityFieldsSchema = (type: CaType) =>
z.object({
name: slugSchema({ field: "name" }).optional(),
disableDirectIssuance: z.boolean().optional()
});
@@ -1,5 +1,300 @@
type TCertificateAuthorityServiceFactoryDep = {}; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType, ProjectType, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { TProjectDALFactory } from "../project/project-dal";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { CaType } from "./certificate-authority-enums";
import {
TCertificateAuthority,
TCreateCertificateAuthorityDTO,
TUpdateCertificateAuthorityDTO
} from "./certificate-authority-types";
import { TInternalCertificateAuthorityServiceFactory } from "./internal/internal-certificate-authority-service";
type TCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick<
TCertificateAuthorityDALFactory,
| "transaction"
| "create"
| "findById"
| "updateById"
| "deleteById"
| "findOne"
| "findByIdWithAssociatedCa"
| "findWithAssociatedCa"
>;
internalCertificateAuthorityService: TInternalCertificateAuthorityServiceFactory;
projectDAL: Pick<
TProjectDALFactory,
"findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>; export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
export const certificateAuthorityServiceFactory = ({}: TCertificateAuthorityServiceFactoryDep) => {}; export const certificateAuthorityServiceFactory = ({
certificateAuthorityDAL,
projectDAL,
permissionService,
internalCertificateAuthorityService
}: TCertificateAuthorityServiceFactoryDep) => {
const createCertificateAuthority = async (
{ type, projectId, configuration, disableDirectIssuance }: TCreateCertificateAuthorityDTO,
actor: OrgServiceActor
) => {
let finalProjectId: string = projectId;
const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId(
projectId,
ProjectType.CertificateManager
);
if (certManagerProjectFromSplit) {
finalProjectId = certManagerProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: finalProjectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.CertificateAuthorities
);
if (type === CaType.INTERNAL) {
const ca = await internalCertificateAuthorityService.createCa({
...configuration,
isInternal: true,
projectId: finalProjectId,
requireTemplateForIssuance: disableDirectIssuance
});
if (!ca.internalCa) {
throw new BadRequestError({
message: "Failed to create internal certificate authority"
});
}
return {
id: ca.id,
type,
disableDirectIssuance: ca.disableDirectIssuance,
name: ca.internalCa?.friendlyName,
projectId,
configuration: ca.internalCa
} as TCertificateAuthority;
}
};
const findCertificateAuthorityById = async (
{ certificateAuthorityId, type }: { certificateAuthorityId: string; type: CaType },
actor: OrgServiceActor
) => {
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(certificateAuthorityId);
if (!certificateAuthority)
throw new NotFoundError({
message: `Could not find certificate authority with ID "${certificateAuthorityId}"`
});
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.CertificateAuthorities
);
if (type === CaType.INTERNAL) {
if (!certificateAuthority.internalCa) {
throw new NotFoundError({
message: `Could not find internal certificate authority with ID "${certificateAuthorityId}"`
});
}
return {
id: certificateAuthority.id,
type,
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
name: certificateAuthority.internalCa.friendlyName,
projectId: certificateAuthority.projectId,
configuration: certificateAuthority.internalCa
} as TCertificateAuthority;
}
};
const listCertificateAuthoritiesByProjectId = async (
{ projectId, type }: { projectId: string; type: CaType },
actor: OrgServiceActor
) => {
let finalProjectId: string = projectId;
const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId(
projectId,
ProjectType.CertificateManager
);
if (certManagerProjectFromSplit) {
finalProjectId = certManagerProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: finalProjectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.CertificateAuthorities
);
const cas = await certificateAuthorityDAL.findWithAssociatedCa({
[`${TableName.CertificateAuthority}.projectId` as "projectId"]: finalProjectId,
...(type === CaType.INTERNAL && {
$notNull: [`${TableName.InternalCertificateAuthority}.id` as "id"]
}),
...(type !== CaType.INTERNAL && {
[`${TableName.ExternalCertificateAuthority}.type` as "type"]: type
})
});
if (type === CaType.INTERNAL) {
return cas
.filter((ca): ca is typeof ca & { internalCa: NonNullable<typeof ca.internalCa> } => Boolean(ca.internalCa))
.map((ca) => ({
id: ca.id,
type,
disableDirectIssuance: ca.disableDirectIssuance,
name: ca.internalCa.friendlyName,
projectId: ca.projectId,
configuration: ca.internalCa
})) as TCertificateAuthority[];
}
};
const updateCertificateAuthority = async (
{ id, type, configuration, disableDirectIssuance }: TUpdateCertificateAuthorityDTO,
actor: OrgServiceActor
) => {
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(id);
if (!certificateAuthority)
throw new NotFoundError({
message: `Could not find certificate authority with ID "${id}"`
});
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.CertificateAuthorities
);
if (type === CaType.INTERNAL) {
if (!certificateAuthority.internalCa) {
throw new NotFoundError({
message: `Could not find internal certificate authority with ID "${id}"`
});
}
const updatedCa = await internalCertificateAuthorityService.updateCaById({
...configuration,
isInternal: true,
requireTemplateForIssuance: disableDirectIssuance,
caId: id
});
if (!updatedCa.internalCa) {
throw new BadRequestError({
message: "Failed to update internal certificate authority"
});
}
return {
id: updatedCa.id,
type,
disableDirectIssuance: updatedCa.disableDirectIssuance,
name: updatedCa.internalCa?.friendlyName,
projectId: updatedCa.projectId,
configuration: updatedCa.internalCa
} as TCertificateAuthority;
}
};
const deleteCertificateAuthority = async ({ id, type }: { id: string; type: CaType }, actor: OrgServiceActor) => {
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(id);
if (!certificateAuthority)
throw new NotFoundError({
message: `Could not find certificate authority with ID "${id}"`
});
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.CertificateAuthorities
);
if (!certificateAuthority.internalCa && type === CaType.INTERNAL) {
throw new BadRequestError({
message: "Certificate authority cannot be deleted due to mismatching type"
});
}
await certificateAuthorityDAL.deleteById(id);
if (type === CaType.INTERNAL) {
return {
id: certificateAuthority.id,
type,
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
name: certificateAuthority.internalCa?.friendlyName,
projectId: certificateAuthority.projectId,
configuration: certificateAuthority.internalCa
} as TCertificateAuthority;
}
};
return {
createCertificateAuthority,
findCertificateAuthorityById,
listCertificateAuthoritiesByProjectId,
updateCertificateAuthority,
deleteCertificateAuthority
};
};
@@ -1,186 +1,18 @@
import { TProjectPermission } from "@app/lib/types"; import { CaType } from "./certificate-authority-enums";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import {
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; TInternalCertificateAuthority,
import { TProjectDALFactory } from "@app/services/project/project-dal"; TInternalCertificateAuthorityInput
} from "./internal/internal-certificate-authority-types";
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal"; export type TCertificateAuthority = TInternalCertificateAuthority;
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types";
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
export enum CaType { export type TCertificateAuthorityInput = TInternalCertificateAuthorityInput;
ROOT = "root",
INTERMEDIATE = "intermediate"
}
export enum CaStatus { export type TCreateCertificateAuthorityDTO = Omit<TCertificateAuthority, "type" | "id"> & {
ACTIVE = "active",
DISABLED = "disabled",
PENDING_CERTIFICATE = "pending-certificate"
}
export enum CaRenewalType {
EXISTING = "existing"
}
export type TCreateCaDTO = {
projectSlug: string;
type: CaType; type: CaType;
friendlyName?: string;
commonName: string;
organization: string;
ou: string;
country: string;
province: string;
locality: string;
notBefore?: string;
notAfter?: string;
maxPathLength: number;
keyAlgorithm: CertKeyAlgorithm;
requireTemplateForIssuance: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateCaDTO = {
caId: string;
status?: CaStatus;
requireTemplateForIssuance?: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteCaDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaCsrDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TRenewCaCertDTO = {
caId: string;
notAfter: string;
type: CaRenewalType;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaCertsDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaCertDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TSignIntermediateDTO = {
caId: string;
csr: string;
notBefore?: string;
notAfter: string;
maxPathLength: number;
} & Omit<TProjectPermission, "projectId">;
export type TImportCertToCaDTO = {
caId: string;
certificate: string;
certificateChain: string;
} & Omit<TProjectPermission, "projectId">;
export type TIssueCertFromCaDTO = {
caId?: string;
certificateTemplateId?: string;
pkiCollectionId?: string;
friendlyName?: string;
commonName: string;
altNames: string;
ttl: string;
notBefore?: string;
notAfter?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">;
export type TSignCertFromCaDTO =
| {
isInternal: true;
caId?: string;
csr: string;
certificateTemplateId?: string;
pkiCollectionId?: string;
friendlyName?: string;
commonName?: string;
altNames?: string;
ttl?: string;
notBefore?: string;
notAfter?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
}
| ({
isInternal: false;
caId?: string;
csr: string;
certificateTemplateId?: string;
pkiCollectionId?: string;
friendlyName?: string;
commonName?: string;
altNames: string;
ttl: string;
notBefore?: string;
notAfter?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">);
export type TGetCaCertificateTemplatesDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TDNParts = {
commonName?: string;
organization?: string;
ou?: string;
country?: string;
province?: string;
locality?: string;
}; };
export type TGetCaCredentialsDTO = { export type TUpdateCertificateAuthorityDTO = Partial<Omit<TCreateCertificateAuthorityDTO, "projectId">> & {
caId: string; type: CaType;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">; id: string;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TGetCaCertChainsDTO = {
caId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "find">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TGetCaCertChainDTO = {
caCertId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TRebuildCaCrlDTO = {
caId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
certificateDAL: Pick<TCertificateDALFactory, "find">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
};
export type TRotateCaCrlTriggerDTO = {
caId: string;
rotationIntervalDays: number;
}; };
@@ -0,0 +1,58 @@
import { z } from "zod";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import { CaType, InternalCaType } from "../certificate-authority-enums";
import {
BaseCertificateAuthoritySchema,
GenericCreateCertificateAuthorityFieldsSchema,
GenericUpdateCertificateAuthorityFieldsSchema
} from "../certificate-authority-schemas";
import { validateCaDateField } from "../certificate-authority-validators";
const InternalCertificateAuthorityConfigurationSchema = z
.object({
type: z.nativeEnum(InternalCaType),
friendlyName: z.string().optional(),
commonName: z.string().trim(),
organization: z.string().trim(),
ou: z.string().trim(),
country: z.string().trim(),
province: z.string().trim(),
locality: z.string().trim(),
// format: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
maxPathLength: z.number().min(-1),
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm)
})
.refine(
(data) => {
// Check that at least one of the specified fields is non-empty
return [data.commonName, data.organization, data.ou, data.country, data.province, data.locality].some(
(field) => field !== ""
);
},
{
message:
"At least one of the fields commonName, organization, ou, country, province, or locality must be non-empty",
path: []
}
);
export const InternalCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.INTERNAL).extend({
type: z.literal(CaType.INTERNAL),
configuration: InternalCertificateAuthorityConfigurationSchema
});
export const CreateInternalCertificateAuthoritySchema = GenericCreateCertificateAuthorityFieldsSchema(
CaType.INTERNAL
).extend({
configuration: InternalCertificateAuthorityConfigurationSchema
});
export const UpdateInternalCertificateAuthoritySchema = GenericUpdateCertificateAuthorityFieldsSchema(
CaType.INTERNAL
).extend({
configuration: InternalCertificateAuthorityConfigurationSchema.optional()
});
@@ -30,19 +30,20 @@ import { TPkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-c
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "../../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import { TCertificateSecretDALFactory } from "../../certificate/certificate-secret-dal";
import { import {
CertExtendedKeyUsage, CertExtendedKeyUsage,
CertExtendedKeyUsageOIDToName, CertExtendedKeyUsageOIDToName,
CertKeyAlgorithm, CertKeyAlgorithm,
CertKeyUsage, CertKeyUsage,
CertStatus CertStatus
} from "../certificate/certificate-types"; } from "../../certificate/certificate-types";
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal"; import { TCertificateTemplateDALFactory } from "../../certificate-template/certificate-template-dal";
import { validateCertificateDetailsAgainstTemplate } from "../certificate-template/certificate-template-fns"; import { validateCertificateDetailsAgainstTemplate } from "../../certificate-template/certificate-template-fns";
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "../certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory, TCertificateAuthorityWithAssociatedCa } from "./certificate-authority-dal"; import { TCertificateAuthorityDALFactory, TCertificateAuthorityWithAssociatedCa } from "../certificate-authority-dal";
import { CaStatus, InternalCaType } from "../certificate-authority-enums";
import { import {
createDistinguishedName, createDistinguishedName,
createSerialNumber, createSerialNumber,
@@ -52,12 +53,11 @@ import {
getCaCredentials, getCaCredentials,
keyAlgorithmToAlgCfg, keyAlgorithmToAlgCfg,
parseDistinguishedName parseDistinguishedName
} from "./certificate-authority-fns"; } from "../certificate-authority-fns";
import { TCertificateAuthorityQueueFactory } from "./certificate-authority-queue"; import { TCertificateAuthorityQueueFactory } from "../certificate-authority-queue";
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
import { TInternalCertificateAuthorityDALFactory } from "./internal-certificate-authority-dal";
import { import {
CaStatus,
CaType,
TCreateCaDTO, TCreateCaDTO,
TDeleteCaDTO, TDeleteCaDTO,
TGetCaCertDTO, TGetCaCertDTO,
@@ -71,8 +71,7 @@ import {
TSignCertFromCaDTO, TSignCertFromCaDTO,
TSignIntermediateDTO, TSignIntermediateDTO,
TUpdateCaDTO TUpdateCaDTO
} from "./certificate-authority-types"; } from "./internal-certificate-authority-types";
import { TInternalCertificateAuthorityDALFactory } from "./internal-certificate-authority-dal";
type TInternalCertificateAuthorityServiceFactoryDep = { type TInternalCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick< certificateAuthorityDAL: Pick<
@@ -130,7 +129,6 @@ export const internalCertificateAuthorityServiceFactory = ({
permissionService permissionService
}: TInternalCertificateAuthorityServiceFactoryDep) => { }: TInternalCertificateAuthorityServiceFactoryDep) => {
const createCa = async ({ const createCa = async ({
projectSlug,
type, type,
friendlyName, friendlyName,
commonName, commonName,
@@ -144,37 +142,39 @@ export const internalCertificateAuthorityServiceFactory = ({
maxPathLength, maxPathLength,
keyAlgorithm, keyAlgorithm,
requireTemplateForIssuance, requireTemplateForIssuance,
actorId, ...dto
actorAuthMethod,
actor,
actorOrgId
}: TCreateCaDTO) => { }: TCreateCaDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); let projectId: string;
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!dto.isInternal) {
let projectId = project.id; const project = await projectDAL.findProjectBySlug(dto.projectSlug, dto.actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${dto.projectSlug}' not found` });
projectId = project.id;
const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId(
projectId, projectId,
ProjectType.CertificateManager ProjectType.CertificateManager
); );
if (certManagerProjectFromSplit) { if (certManagerProjectFromSplit) {
projectId = certManagerProjectFromSplit.id; projectId = certManagerProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission({
actor: dto.actor,
actorId: dto.actorId,
projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.CertificateAuthorities
);
} else {
projectId = dto.projectId;
} }
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.CertificateAuthorities
);
const dn = createDistinguishedName({ const dn = createDistinguishedName({
commonName, commonName,
organization, organization,
@@ -216,10 +216,10 @@ export const internalCertificateAuthorityServiceFactory = ({
locality, locality,
friendlyName: friendlyName || dn, friendlyName: friendlyName || dn,
commonName, commonName,
status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE, status: type === InternalCaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE,
dn, dn,
keyAlgorithm, keyAlgorithm,
...(type === CaType.ROOT && { ...(type === InternalCaType.ROOT && {
maxPathLength, maxPathLength,
notBefore: notBeforeDate, notBefore: notBeforeDate,
notAfter: notAfterDate, notAfter: notAfterDate,
@@ -256,7 +256,7 @@ export const internalCertificateAuthorityServiceFactory = ({
tx tx
); );
if (type === CaType.ROOT) { if (type === InternalCaType.ROOT) {
// note: create self-signed cert only applicable for root CA // note: create self-signed cert only applicable for root CA
const cert = await x509.X509CertificateGenerator.createSelfSigned({ const cert = await x509.X509CertificateGenerator.createSelfSigned({
name: dn, name: dn,
@@ -357,31 +357,25 @@ export const internalCertificateAuthorityServiceFactory = ({
* Update CA with id [caId]. * Update CA with id [caId].
* Note: Used to enable/disable CA * Note: Used to enable/disable CA
*/ */
const updateCaById = async ({ const updateCaById = async ({ caId, status, requireTemplateForIssuance, ...dto }: TUpdateCaDTO) => {
caId,
status,
requireTemplateForIssuance,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TUpdateCaDTO) => {
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId); const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
if (!ca.internalCa) throw new NotFoundError({ message: `CA with ID '${caId}' not found` }); if (!ca.internalCa) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission({ if (!dto.isInternal) {
actor, const { permission } = await permissionService.getProjectPermission({
actorId, actor: dto.actor,
projectId: ca.projectId, actorId: dto.actorId,
actorAuthMethod, projectId: ca.projectId,
actorOrgId, actorAuthMethod: dto.actorAuthMethod,
actionProjectType: ActionProjectType.CertificateManager actorOrgId: dto.actorOrgId,
}); actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.CertificateAuthorities ProjectPermissionSub.CertificateAuthorities
); );
}
const updatedCa = await certificateAuthorityDAL.transaction(async (tx) => { const updatedCa = await certificateAuthorityDAL.transaction(async (tx) => {
if (status !== undefined) { if (status !== undefined) {
@@ -451,7 +445,8 @@ export const internalCertificateAuthorityServiceFactory = ({
ProjectPermissionSub.CertificateAuthorities ProjectPermissionSub.CertificateAuthorities
); );
if (ca.internalCa.type === CaType.ROOT) throw new BadRequestError({ message: "Root CA cannot generate CSR" }); if (ca.internalCa.type === InternalCaType.ROOT)
throw new BadRequestError({ message: "Root CA cannot generate CSR" });
const { caPrivateKey, caPublicKey } = await getCaCredentials({ const { caPrivateKey, caPublicKey } = await getCaCredentials({
caId, caId,
@@ -554,7 +549,7 @@ export const internalCertificateAuthorityServiceFactory = ({
let certificateChain = ""; let certificateChain = "";
switch (ca.internalCa.type) { switch (ca.internalCa.type) {
case CaType.ROOT: { case InternalCaType.ROOT: {
if (new Date(notAfter) <= new Date(caCertObj.notAfter)) { if (new Date(notAfter) <= new Date(caCertObj.notAfter)) {
throw new BadRequestError({ throw new BadRequestError({
message: message:
@@ -623,7 +618,7 @@ export const internalCertificateAuthorityServiceFactory = ({
certificate = cert.toString("pem"); certificate = cert.toString("pem");
break; break;
} }
case CaType.INTERMEDIATE: { case InternalCaType.INTERMEDIATE: {
if (!ca.internalCa.parentCaId) { if (!ca.internalCa.parentCaId) {
// TODO: look into optimal way to support renewal of intermediate CA with external parent CA // TODO: look into optimal way to support renewal of intermediate CA with external parent CA
throw new BadRequestError({ throw new BadRequestError({
@@ -0,0 +1,209 @@
import { z } from "zod";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TProjectPermission } from "@app/lib/types";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal";
import { CaRenewalType, CaStatus, InternalCaType } from "../certificate-authority-enums";
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
import {
CreateInternalCertificateAuthoritySchema,
InternalCertificateAuthoritySchema
} from "./internal-certificate-authority-schemas";
export type TInternalCertificateAuthority = z.infer<typeof InternalCertificateAuthoritySchema>;
export type TInternalCertificateAuthorityInput = z.infer<typeof CreateInternalCertificateAuthoritySchema>;
export type TCreateCaDTO =
| {
isInternal: true;
projectId: string;
type: InternalCaType;
friendlyName?: string;
commonName: string;
organization: string;
ou: string;
country: string;
province: string;
locality: string;
notBefore?: string;
notAfter?: string;
maxPathLength: number;
keyAlgorithm: CertKeyAlgorithm;
requireTemplateForIssuance: boolean;
}
| ({
isInternal: false;
projectSlug: string;
type: InternalCaType;
friendlyName?: string;
commonName: string;
organization: string;
ou: string;
country: string;
province: string;
locality: string;
notBefore?: string;
notAfter?: string;
maxPathLength: number;
keyAlgorithm: CertKeyAlgorithm;
requireTemplateForIssuance: boolean;
} & Omit<TProjectPermission, "projectId">);
export type TGetCaDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateCaDTO =
| {
isInternal: true;
caId: string;
status?: CaStatus;
requireTemplateForIssuance?: boolean;
}
| ({
isInternal: false;
caId: string;
status?: CaStatus;
requireTemplateForIssuance?: boolean;
} & Omit<TProjectPermission, "projectId">);
export type TDeleteCaDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaCsrDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TRenewCaCertDTO = {
caId: string;
notAfter: string;
type: CaRenewalType;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaCertsDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetCaCertDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TSignIntermediateDTO = {
caId: string;
csr: string;
notBefore?: string;
notAfter: string;
maxPathLength: number;
} & Omit<TProjectPermission, "projectId">;
export type TImportCertToCaDTO = {
caId: string;
certificate: string;
certificateChain: string;
} & Omit<TProjectPermission, "projectId">;
export type TIssueCertFromCaDTO = {
caId?: string;
certificateTemplateId?: string;
pkiCollectionId?: string;
friendlyName?: string;
commonName: string;
altNames: string;
ttl: string;
notBefore?: string;
notAfter?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">;
export type TSignCertFromCaDTO =
| {
isInternal: true;
caId?: string;
csr: string;
certificateTemplateId?: string;
pkiCollectionId?: string;
friendlyName?: string;
commonName?: string;
altNames?: string;
ttl?: string;
notBefore?: string;
notAfter?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
}
| ({
isInternal: false;
caId?: string;
csr: string;
certificateTemplateId?: string;
pkiCollectionId?: string;
friendlyName?: string;
commonName?: string;
altNames: string;
ttl: string;
notBefore?: string;
notAfter?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">);
export type TGetCaCertificateTemplatesDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TDNParts = {
commonName?: string;
organization?: string;
ou?: string;
country?: string;
province?: string;
locality?: string;
};
export type TGetCaCredentialsDTO = {
caId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TGetCaCertChainsDTO = {
caId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "find">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TGetCaCertChainDTO = {
caCertId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
};
export type TRebuildCaCrlDTO = {
caId: string;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
certificateDAL: Pick<TCertificateDALFactory, "find">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
};
export type TRotateCaCrlTriggerDTO = {
caId: string;
rotationIntervalDays: number;
};
@@ -27,6 +27,7 @@ import {
} from "@app/services/certificate/certificate-types"; } from "@app/services/certificate/certificate-types";
import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums";
import { import {
createSerialNumber, createSerialNumber,
expandInternalCa, expandInternalCa,
@@ -36,7 +37,6 @@ import {
parseDistinguishedName parseDistinguishedName
} from "@app/services/certificate-authority/certificate-authority-fns"; } from "@app/services/certificate-authority/certificate-authority-fns";
import { TCertificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal"; import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";