mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Start moving CRL generation to queue
This commit is contained in:
8
backend/src/@types/knex.d.ts
vendored
8
backend/src/@types/knex.d.ts
vendored
@@ -38,6 +38,9 @@ import {
|
|||||||
TCertificateAuthorityCerts,
|
TCertificateAuthorityCerts,
|
||||||
TCertificateAuthorityCertsInsert,
|
TCertificateAuthorityCertsInsert,
|
||||||
TCertificateAuthorityCertsUpdate,
|
TCertificateAuthorityCertsUpdate,
|
||||||
|
TCertificateAuthorityCrl,
|
||||||
|
TCertificateAuthorityCrlInsert,
|
||||||
|
TCertificateAuthorityCrlUpdate,
|
||||||
TCertificateAuthoritySk,
|
TCertificateAuthoritySk,
|
||||||
TCertificateAuthoritySkInsert,
|
TCertificateAuthoritySkInsert,
|
||||||
TCertificateAuthoritySkUpdate,
|
TCertificateAuthoritySkUpdate,
|
||||||
@@ -279,6 +282,11 @@ declare module "knex/types/tables" {
|
|||||||
TCertificateAuthoritySkInsert,
|
TCertificateAuthoritySkInsert,
|
||||||
TCertificateAuthoritySkUpdate
|
TCertificateAuthoritySkUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.CertificateAuthorityCrl]: Knex.CompositeTableType<
|
||||||
|
TCertificateAuthorityCrl,
|
||||||
|
TCertificateAuthorityCrlInsert,
|
||||||
|
TCertificateAuthorityCrlUpdate
|
||||||
|
>;
|
||||||
[TableName.Certificate]: Knex.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
[TableName.Certificate]: Knex.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
||||||
[TableName.CertificateCert]: Knex.CompositeTableType<
|
[TableName.CertificateCert]: Knex.CompositeTableType<
|
||||||
TCertificateCerts,
|
TCertificateCerts,
|
||||||
|
|||||||
@@ -54,6 +54,19 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.CertificateAuthorityCrl))) {
|
||||||
|
await knex.schema.createTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("caId").notNullable().unique();
|
||||||
|
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||||
|
t.text("crl").notNullable(); // TODO: encrypt
|
||||||
|
t.integer("ttl").notNullable(); // in minutes
|
||||||
|
// TODO: consider type (crl or delta)
|
||||||
|
// TODO: rebuild interval
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await knex.schema.hasTable(TableName.Certificate))) {
|
if (!(await knex.schema.hasTable(TableName.Certificate))) {
|
||||||
await knex.schema.createTable(TableName.Certificate, (t) => {
|
await knex.schema.createTable(TableName.Certificate, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
@@ -100,6 +113,9 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.dropTableIfExists(TableName.CertificateAuthoritySk);
|
await knex.schema.dropTableIfExists(TableName.CertificateAuthoritySk);
|
||||||
await dropOnUpdateTrigger(knex, TableName.CertificateAuthoritySk);
|
await dropOnUpdateTrigger(knex, TableName.CertificateAuthoritySk);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.CertificateAuthorityCrl);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.CertificateAuthorityCrl);
|
||||||
|
|
||||||
await knex.schema.dropTableIfExists(TableName.CertificateAuthorityCert);
|
await knex.schema.dropTableIfExists(TableName.CertificateAuthorityCert);
|
||||||
await dropOnUpdateTrigger(knex, TableName.CertificateAuthorityCert);
|
await dropOnUpdateTrigger(knex, TableName.CertificateAuthorityCert);
|
||||||
|
|
||||||
|
|||||||
23
backend/src/db/schemas/certificate-authority-crl.ts
Normal file
23
backend/src/db/schemas/certificate-authority-crl.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// 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 CertificateAuthorityCrlSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
caId: z.string().uuid(),
|
||||||
|
crl: z.string(),
|
||||||
|
ttl: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCertificateAuthorityCrl = z.infer<typeof CertificateAuthorityCrlSchema>;
|
||||||
|
export type TCertificateAuthorityCrlInsert = Omit<z.input<typeof CertificateAuthorityCrlSchema>, TImmutableDBKeys>;
|
||||||
|
export type TCertificateAuthorityCrlUpdate = Partial<
|
||||||
|
Omit<z.input<typeof CertificateAuthorityCrlSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -10,6 +10,7 @@ export * from "./auth-tokens";
|
|||||||
export * from "./backup-private-key";
|
export * from "./backup-private-key";
|
||||||
export * from "./certificate-authorities";
|
export * from "./certificate-authorities";
|
||||||
export * from "./certificate-authority-certs";
|
export * from "./certificate-authority-certs";
|
||||||
|
export * from "./certificate-authority-crl";
|
||||||
export * from "./certificate-authority-sk";
|
export * from "./certificate-authority-sk";
|
||||||
export * from "./certificate-certs";
|
export * from "./certificate-certs";
|
||||||
export * from "./certificate-secrets";
|
export * from "./certificate-secrets";
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export enum TableName {
|
|||||||
CertificateAuthority = "certificate_authorities",
|
CertificateAuthority = "certificate_authorities",
|
||||||
CertificateAuthorityCert = "certificate_authority_certs",
|
CertificateAuthorityCert = "certificate_authority_certs",
|
||||||
CertificateAuthoritySk = "certificate_authority_sk",
|
CertificateAuthoritySk = "certificate_authority_sk",
|
||||||
|
CertificateAuthorityCrl = "certificate_authority_crl",
|
||||||
Certificate = "certificates",
|
Certificate = "certificates",
|
||||||
CertificateCert = "certificate_certs",
|
CertificateCert = "certificate_certs",
|
||||||
CertificateSecret = "certificate_secrets",
|
CertificateSecret = "certificate_secrets",
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export enum QueueName {
|
|||||||
SecretFullRepoScan = "secret-full-repo-scan",
|
SecretFullRepoScan = "secret-full-repo-scan",
|
||||||
SecretPushEventScan = "secret-push-event-scan",
|
SecretPushEventScan = "secret-push-event-scan",
|
||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
||||||
DynamicSecretRevocation = "dynamic-secret-revocation"
|
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||||
|
CaCrlRotation = "ca-crl-rotation"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@@ -37,7 +38,8 @@ export enum QueueJobs {
|
|||||||
SecretScan = "secret-scan",
|
SecretScan = "secret-scan",
|
||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost-job",
|
UpgradeProjectToGhost = "upgrade-project-to-ghost-job",
|
||||||
DynamicSecretRevocation = "dynamic-secret-revocation",
|
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||||
DynamicSecretPruning = "dynamic-secret-pruning"
|
DynamicSecretPruning = "dynamic-secret-pruning",
|
||||||
|
CaCrlRotation = "ca-crl-rotation-job"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -50,7 +52,6 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
name: QueueJobs.SecretReminder;
|
name: QueueJobs.SecretReminder;
|
||||||
};
|
};
|
||||||
|
|
||||||
[QueueName.SecretRotation]: {
|
[QueueName.SecretRotation]: {
|
||||||
payload: { rotationId: string };
|
payload: { rotationId: string };
|
||||||
name: QueueJobs.SecretRotation;
|
name: QueueJobs.SecretRotation;
|
||||||
@@ -116,6 +117,12 @@ export type TQueueJobTypes = {
|
|||||||
dynamicSecretCfgId: string;
|
dynamicSecretCfgId: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
[QueueName.CaCrlRotation]: {
|
||||||
|
name: QueueJobs.CaCrlRotation;
|
||||||
|
payload: {
|
||||||
|
caId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ import { certificateCertDALFactory } from "@app/services/certificate/certificate
|
|||||||
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { certificateServiceFactory } from "@app/services/certificate/certificate-service";
|
import { certificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||||
import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
|
import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
|
||||||
|
import { certificateAuthorityCrlDALFactory } from "@app/services/certificate-authority/certificate-authority-crl-dal";
|
||||||
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
|
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
|
||||||
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
import { certificateAuthoritySkDALFactory } from "@app/services/certificate-authority/certificate-authority-sk-dal";
|
import { certificateAuthoritySkDALFactory } from "@app/services/certificate-authority/certificate-authority-sk-dal";
|
||||||
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
@@ -503,6 +505,7 @@ export const registerRoutes = async (
|
|||||||
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
|
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
|
||||||
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
|
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
|
||||||
const certificateAuthoritySkDAL = certificateAuthoritySkDALFactory(db);
|
const certificateAuthoritySkDAL = certificateAuthoritySkDALFactory(db);
|
||||||
|
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
|
||||||
|
|
||||||
const certificateDAL = certificateDALFactory(db);
|
const certificateDAL = certificateDALFactory(db);
|
||||||
const certificateCertDAL = certificateCertDALFactory(db);
|
const certificateCertDAL = certificateCertDALFactory(db);
|
||||||
@@ -514,10 +517,20 @@ export const registerRoutes = async (
|
|||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const certificateAuthorityQueue = certificateAuthorityQueueFactory({
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySkDAL,
|
||||||
|
certificateDAL,
|
||||||
|
queueService
|
||||||
|
});
|
||||||
|
|
||||||
const certificateAuthorityService = certificateAuthorityServiceFactory({
|
const certificateAuthorityService = certificateAuthorityServiceFactory({
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
certificateAuthoritySkDAL,
|
certificateAuthoritySkDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateAuthorityQueue,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateCertDAL,
|
certificateCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
|||||||
@@ -419,4 +419,36 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:caId/crl/rotate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Rotate CRL of the CA",
|
||||||
|
params: z.object({
|
||||||
|
caId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
await server.services.certificateAuthority.rotateCaCrl({
|
||||||
|
caId: req.params.caId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
message: "Successfully rotated CA CRL"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TCertificateAuthorityCrlDALFactory = ReturnType<typeof certificateAuthorityCrlDALFactory>;
|
||||||
|
|
||||||
|
export const certificateAuthorityCrlDALFactory = (db: TDbClient) => {
|
||||||
|
const caCrlOrm = ormify(db, TableName.CertificateAuthorityCrl);
|
||||||
|
return caCrlOrm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
|
import { CertKeyAlgorithm, CertStatus } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
|
import { TCertificateAuthorityCrlDALFactory } from "./certificate-authority-crl-dal";
|
||||||
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
|
import { keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
|
||||||
|
import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal";
|
||||||
|
import { TRotateCaCrlTriggerDTO } from "./certificate-authority-types";
|
||||||
|
|
||||||
|
type TCertificateAuthorityQueueFactoryDep = {
|
||||||
|
// TODO: Pick
|
||||||
|
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
||||||
|
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
|
||||||
|
certificateAuthoritySkDAL: TCertificateAuthoritySkDALFactory;
|
||||||
|
certificateDAL: TCertificateDALFactory;
|
||||||
|
queueService: TQueueServiceFactory;
|
||||||
|
};
|
||||||
|
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
|
||||||
|
|
||||||
|
export const certificateAuthorityQueueFactory = ({
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySkDAL,
|
||||||
|
certificateDAL,
|
||||||
|
queueService
|
||||||
|
}: TCertificateAuthorityQueueFactoryDep) => {
|
||||||
|
// TODO 1: auto-periodic rotation
|
||||||
|
// TODO 2: manual rotation
|
||||||
|
|
||||||
|
const setCaCrlRotationInterval = async ({ caId, rotationIntervalDays }: TRotateCaCrlTriggerDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
// query for config
|
||||||
|
// const caCrl = await certificateAuthorityCrlDAL.findOne({
|
||||||
|
// caId
|
||||||
|
// });
|
||||||
|
|
||||||
|
await queueService.queue(
|
||||||
|
// TODO: clarify queue + job naming
|
||||||
|
QueueName.CaCrlRotation,
|
||||||
|
QueueJobs.CaCrlRotation,
|
||||||
|
{
|
||||||
|
caId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: `ca-crl-rotation-${caId}`,
|
||||||
|
repeat: {
|
||||||
|
// on prod it this will be in days, in development this will be second
|
||||||
|
every:
|
||||||
|
appCfg.NODE_ENV === "development"
|
||||||
|
? secondsToMillis(rotationIntervalDays)
|
||||||
|
: daysToMillisecond(rotationIntervalDays),
|
||||||
|
immediately: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.CaCrlRotation, async (job) => {
|
||||||
|
const { caId } = job.data;
|
||||||
|
logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
|
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" });
|
||||||
|
const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
|
||||||
|
"sign"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const revokedCerts = await certificateDAL.find({
|
||||||
|
caId: ca.id,
|
||||||
|
status: CertStatus.REVOKED
|
||||||
|
});
|
||||||
|
|
||||||
|
const crl = await x509.X509CrlGenerator.create({
|
||||||
|
issuer: ca.dn,
|
||||||
|
thisUpdate: new Date(),
|
||||||
|
nextUpdate: new Date("2025/12/12"), // TODO: depends on configured rebuild interval
|
||||||
|
entries: revokedCerts.map((revokedCert) => {
|
||||||
|
return {
|
||||||
|
serialNumber: revokedCert.serialNumber,
|
||||||
|
revocationDate: new Date(revokedCert.revokedAt as Date),
|
||||||
|
reason: revokedCert.revocationReason as number,
|
||||||
|
invalidity: new Date("2022/01/01"),
|
||||||
|
issuer: ca.dn
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
signingKey: sk
|
||||||
|
});
|
||||||
|
|
||||||
|
const base64crl = crl.toString("base64");
|
||||||
|
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
|
||||||
|
|
||||||
|
await certificateAuthorityCrlDAL.update(
|
||||||
|
{
|
||||||
|
caId: ca.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
crl: crlPem // TODO: encrypt
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
queueService.listen(QueueName.CaCrlRotation, "failed", (job, err) => {
|
||||||
|
logger.error(err, "Failed to rotate CA CRL %s", job?.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
setCaCrlRotationInterval
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -12,8 +12,10 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
|
|
||||||
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
|
import { TCertificateAuthorityCrlDALFactory } from "./certificate-authority-crl-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
import { createDistinguishedName, keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
|
import { createDistinguishedName, keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
|
||||||
|
import { TCertificateAuthorityQueueFactory } from "./certificate-authority-queue";
|
||||||
import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal";
|
import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal";
|
||||||
import {
|
import {
|
||||||
CaStatus,
|
CaStatus,
|
||||||
@@ -26,6 +28,7 @@ import {
|
|||||||
TGetCrl,
|
TGetCrl,
|
||||||
TImportCertToCaDTO,
|
TImportCertToCaDTO,
|
||||||
TIssueCertFromCaDTO,
|
TIssueCertFromCaDTO,
|
||||||
|
TRotateCrlDTO,
|
||||||
TSignIntermediateDTO,
|
TSignIntermediateDTO,
|
||||||
TUpdateCaDTO
|
TUpdateCaDTO
|
||||||
} from "./certificate-authority-types";
|
} from "./certificate-authority-types";
|
||||||
@@ -37,6 +40,8 @@ type TCertificateAuthorityServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
|
||||||
certificateAuthoritySkDAL: Pick<TCertificateAuthoritySkDALFactory, "create" | "findOne">;
|
certificateAuthoritySkDAL: Pick<TCertificateAuthoritySkDALFactory, "create" | "findOne">;
|
||||||
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
|
||||||
|
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
||||||
certificateCertDAL: Pick<TCertificateCertDALFactory, "create">;
|
certificateCertDAL: Pick<TCertificateCertDALFactory, "create">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
@@ -49,6 +54,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
certificateAuthoritySkDAL,
|
certificateAuthoritySkDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateCertDAL,
|
certificateCertDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -141,6 +147,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: create CRL
|
||||||
|
|
||||||
if (type === CaType.ROOT) {
|
if (type === CaType.ROOT) {
|
||||||
// note: self-signed cert only applicable for root CA
|
// note: self-signed cert only applicable for root CA
|
||||||
|
|
||||||
@@ -168,6 +176,15 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await certificateAuthorityCrlDAL.create(
|
||||||
|
{
|
||||||
|
caId: ca.id,
|
||||||
|
crl: "", // TODO: encrypt
|
||||||
|
ttl: 60 // in minutes
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await certificateAuthoritySkDAL.create(
|
await certificateAuthoritySkDAL.create(
|
||||||
@@ -721,6 +738,70 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rotateCaCrl = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TRotateCrlDTO) => {
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
|
);
|
||||||
|
|
||||||
|
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" });
|
||||||
|
const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
|
||||||
|
"sign"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const revokedCerts = await certificateDAL.find({
|
||||||
|
caId: ca.id,
|
||||||
|
status: CertStatus.REVOKED
|
||||||
|
});
|
||||||
|
|
||||||
|
const crl = await x509.X509CrlGenerator.create({
|
||||||
|
issuer: ca.dn,
|
||||||
|
thisUpdate: new Date(),
|
||||||
|
nextUpdate: new Date("2025/12/12"),
|
||||||
|
entries: revokedCerts.map((revokedCert) => {
|
||||||
|
return {
|
||||||
|
serialNumber: revokedCert.serialNumber,
|
||||||
|
revocationDate: new Date(revokedCert.revokedAt as Date),
|
||||||
|
reason: revokedCert.revocationReason as number,
|
||||||
|
invalidity: new Date("2022/01/01"),
|
||||||
|
issuer: ca.dn
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
signingKey: sk
|
||||||
|
});
|
||||||
|
|
||||||
|
const base64crl = crl.toString("base64");
|
||||||
|
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
|
||||||
|
|
||||||
|
await certificateAuthorityCrlDAL.update(
|
||||||
|
{
|
||||||
|
caId: ca.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
crl: crlPem // TODO: encrypt
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
crl: crlPem
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createCa,
|
createCa,
|
||||||
getCaById,
|
getCaById,
|
||||||
@@ -731,6 +812,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
signIntermediate,
|
signIntermediate,
|
||||||
importCertToCa,
|
importCertToCa,
|
||||||
issueCertFromCa,
|
issueCertFromCa,
|
||||||
getCaCrl
|
getCaCrl,
|
||||||
|
rotateCaCrl
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ export type TGetCrl = {
|
|||||||
caId: string;
|
caId: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRotateCrlDTO = {
|
||||||
|
caId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDNParts = {
|
export type TDNParts = {
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
@@ -83,3 +87,8 @@ export type TDNParts = {
|
|||||||
province?: string;
|
province?: string;
|
||||||
locality?: string;
|
locality?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRotateCaCrlTriggerDTO = {
|
||||||
|
caId: string;
|
||||||
|
rotationIntervalDays: number;
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||||||
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { IconButton,Modal, ModalContent } from "@app/components/v2";
|
import { IconButton, Modal, ModalContent } from "@app/components/v2";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { useGetCaCrl } from "@app/hooks/api";
|
import { useGetCaCrl } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
@@ -48,8 +48,22 @@ export const CaCrlModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
<div>
|
<div>
|
||||||
{crl && (
|
{crl && (
|
||||||
<>
|
<>
|
||||||
|
{/* <div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2>Manual CRL Rotation</h2>
|
||||||
|
<Button
|
||||||
|
// isLoading={isLoading}
|
||||||
|
// isDisabled={!isAllowed}
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="submit"
|
||||||
|
// onClick={() => handleAssignment(username, !isPartOfGroup)}
|
||||||
|
onClick={() => {}}
|
||||||
|
>
|
||||||
|
Rotate
|
||||||
|
</Button>
|
||||||
|
</div> */}
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h2>CA CRL</h2>
|
<h2>Certificate Revocation List</h2>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<IconButton
|
<IconButton
|
||||||
ariaLabel="copy icon"
|
ariaLabel="copy icon"
|
||||||
|
|||||||
Reference in New Issue
Block a user