mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 13:28:27 +00:00
misc: moved cert issuance to job
This commit is contained in:
@@ -37,6 +37,8 @@ export const KeyStorePrefixes = {
|
|||||||
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
SecretSyncLock: (syncId: string) => `secret-sync-mutex-${syncId}` as const,
|
SecretSyncLock: (syncId: string) => `secret-sync-mutex-${syncId}` as const,
|
||||||
SecretRotationLock: (rotationId: string) => `secret-rotation-v2-mutex-${rotationId}` as const,
|
SecretRotationLock: (rotationId: string) => `secret-rotation-v2-mutex-${rotationId}` as const,
|
||||||
|
CaOrderCertificateForSubscriberLock: (subscriberId: string) =>
|
||||||
|
`ca-order-certificate-for-subscriber-lock-${subscriberId}` as const,
|
||||||
SecretSyncLastRunTimestamp: (syncId: string) => `secret-sync-last-run-${syncId}` as const,
|
SecretSyncLastRunTimestamp: (syncId: string) => `secret-sync-last-run-${syncId}` as const,
|
||||||
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
||||||
`identity-access-token-status:${identityAccessTokenId}`,
|
`identity-access-token-status:${identityAccessTokenId}`,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||||
import {
|
import {
|
||||||
TFailedIntegrationSyncEmailsPayload,
|
TFailedIntegrationSyncEmailsPayload,
|
||||||
TIntegrationSyncPayload,
|
TIntegrationSyncPayload,
|
||||||
@@ -44,6 +45,7 @@ export enum QueueName {
|
|||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
||||||
DynamicSecretRevocation = "dynamic-secret-revocation",
|
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||||
CaCrlRotation = "ca-crl-rotation",
|
CaCrlRotation = "ca-crl-rotation",
|
||||||
|
CaLifecycle = "ca-lifecycle", // parent queue to ca-order-certificate-for-subscriber
|
||||||
SecretReplication = "secret-replication",
|
SecretReplication = "secret-replication",
|
||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
@@ -84,7 +86,8 @@ export enum QueueJobs {
|
|||||||
SecretRotationV2QueueRotations = "secret-rotation-v2-queue-rotations",
|
SecretRotationV2QueueRotations = "secret-rotation-v2-queue-rotations",
|
||||||
SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets",
|
SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets",
|
||||||
SecretRotationV2SendNotification = "secret-rotation-v2-send-notification",
|
SecretRotationV2SendNotification = "secret-rotation-v2-send-notification",
|
||||||
InvalidateCache = "invalidate-cache"
|
InvalidateCache = "invalidate-cache",
|
||||||
|
CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -245,6 +248,13 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
[QueueName.CaLifecycle]: {
|
||||||
|
name: QueueJobs.CaOrderCertificateForSubscriber;
|
||||||
|
payload: {
|
||||||
|
subscriberId: string;
|
||||||
|
caType: CaType;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
|||||||
@@ -850,16 +850,6 @@ export const registerRoutes = async (
|
|||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
const certificateAuthorityQueue = certificateAuthorityQueueFactory({
|
|
||||||
certificateAuthorityCrlDAL,
|
|
||||||
certificateAuthorityDAL,
|
|
||||||
certificateAuthoritySecretDAL,
|
|
||||||
certificateDAL,
|
|
||||||
projectDAL,
|
|
||||||
kmsService,
|
|
||||||
queueService
|
|
||||||
});
|
|
||||||
|
|
||||||
const sshCertificateAuthorityService = sshCertificateAuthorityServiceFactory({
|
const sshCertificateAuthorityService = sshCertificateAuthorityServiceFactory({
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
@@ -906,24 +896,6 @@ export const registerRoutes = async (
|
|||||||
groupDAL
|
groupDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
|
|
||||||
certificateAuthorityDAL,
|
|
||||||
certificateAuthorityCertDAL,
|
|
||||||
certificateAuthoritySecretDAL,
|
|
||||||
certificateAuthorityCrlDAL,
|
|
||||||
certificateTemplateDAL,
|
|
||||||
certificateAuthorityQueue,
|
|
||||||
certificateDAL,
|
|
||||||
certificateBodyDAL,
|
|
||||||
certificateSecretDAL,
|
|
||||||
pkiCollectionDAL,
|
|
||||||
pkiCollectionItemDAL,
|
|
||||||
projectDAL,
|
|
||||||
internalCertificateAuthorityDAL,
|
|
||||||
kmsService,
|
|
||||||
permissionService
|
|
||||||
});
|
|
||||||
|
|
||||||
const certificateAuthorityCrlService = certificateAuthorityCrlServiceFactory({
|
const certificateAuthorityCrlService = certificateAuthorityCrlServiceFactory({
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCrlDAL,
|
certificateAuthorityCrlDAL,
|
||||||
@@ -943,17 +915,6 @@ export const registerRoutes = async (
|
|||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
const certificateEstService = certificateEstServiceFactory({
|
|
||||||
internalCertificateAuthorityService,
|
|
||||||
certificateTemplateService,
|
|
||||||
certificateTemplateDAL,
|
|
||||||
certificateAuthorityCertDAL,
|
|
||||||
certificateAuthorityDAL,
|
|
||||||
projectDAL,
|
|
||||||
kmsService,
|
|
||||||
licenseService
|
|
||||||
});
|
|
||||||
|
|
||||||
const pkiAlertService = pkiAlertServiceFactory({
|
const pkiAlertService = pkiAlertServiceFactory({
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
@@ -1627,6 +1588,52 @@ export const registerRoutes = async (
|
|||||||
keyStore
|
keyStore
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const certificateAuthorityQueue = certificateAuthorityQueueFactory({
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
certificateDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
queueService,
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
externalCertificateAuthorityDAL,
|
||||||
|
keyStore,
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService
|
||||||
|
});
|
||||||
|
|
||||||
|
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateTemplateDAL,
|
||||||
|
certificateAuthorityQueue,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
pkiCollectionDAL,
|
||||||
|
pkiCollectionItemDAL,
|
||||||
|
projectDAL,
|
||||||
|
internalCertificateAuthorityDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateEstService = certificateEstServiceFactory({
|
||||||
|
internalCertificateAuthorityService,
|
||||||
|
certificateTemplateService,
|
||||||
|
certificateTemplateDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
licenseService
|
||||||
|
});
|
||||||
|
|
||||||
const kmipService = kmipServiceFactory({
|
const kmipService = kmipServiceFactory({
|
||||||
kmipClientDAL,
|
kmipClientDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -1673,7 +1680,12 @@ export const registerRoutes = async (
|
|||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
externalCertificateAuthorityDAL,
|
externalCertificateAuthorityDAL,
|
||||||
internalCertificateAuthorityService
|
internalCertificateAuthorityService,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
kmsService,
|
||||||
|
pkiSubscriberDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
||||||
@@ -1688,9 +1700,7 @@ export const registerRoutes = async (
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService,
|
permissionService,
|
||||||
appConnectionDAL,
|
certificateAuthorityQueue
|
||||||
appConnectionService,
|
|
||||||
externalCertificateAuthorityDAL
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await secretRotationV2QueueServiceFactory({
|
await secretRotationV2QueueServiceFactory({
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
|||||||
schema: {
|
schema: {
|
||||||
hide: false,
|
hide: false,
|
||||||
tags: [ApiDocsTags.PkiSubscribers],
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
description: "Issue certificate",
|
description: "Order certificate",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName)
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName)
|
||||||
}),
|
}),
|
||||||
@@ -297,16 +297,12 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificate),
|
message: z.string().trim()
|
||||||
issuingCaCertificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.issuingCaCertificate),
|
|
||||||
certificateChain: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificateChain),
|
|
||||||
privateKey: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.privateKey),
|
|
||||||
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.serialNumber)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
await server.services.pkiSubscriber.issueSubscriberCert({
|
const subscriber = await server.services.pkiSubscriber.orderSubscriberCert({
|
||||||
subscriberName: req.params.subscriberName,
|
subscriberName: req.params.subscriberName,
|
||||||
projectId: req.body.projectId,
|
projectId: req.body.projectId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -315,35 +311,31 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
|||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
// await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
// ...req.auditLogInfo,
|
||||||
projectId: subscriber.projectId,
|
// projectId: subscriber.projectId,
|
||||||
event: {
|
// event: {
|
||||||
type: EventType.ISSUE_PKI_SUBSCRIBER_CERT,
|
// type: EventType.ISSUE_PKI_SUBSCRIBER_CERT,
|
||||||
metadata: {
|
// metadata: {
|
||||||
subscriberId: subscriber.id,
|
// subscriberId: subscriber.id,
|
||||||
name: subscriber.name,
|
// name: subscriber.name,
|
||||||
serialNumber
|
// serialNumber
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
await server.services.telemetry.sendPostHogEvents({
|
// await server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.IssueCert,
|
// event: PostHogEventTypes.IssueCert,
|
||||||
distinctId: getTelemetryDistinctId(req),
|
// distinctId: getTelemetryDistinctId(req),
|
||||||
properties: {
|
// properties: {
|
||||||
subscriberId: subscriber.id,
|
// subscriberId: subscriber.id,
|
||||||
commonName: subscriber.commonName,
|
// commonName: subscriber.commonName,
|
||||||
...req.auditLogInfo
|
// ...req.auditLogInfo
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate,
|
message: "Successfully placed order for certificate"
|
||||||
certificateChain,
|
|
||||||
issuingCaCertificate,
|
|
||||||
privateKey,
|
|
||||||
serialNumber
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import * as x509 from "@peculiar/x509";
|
|||||||
import acme from "acme-client";
|
import acme from "acme-client";
|
||||||
import { KeyObject } from "crypto";
|
import { KeyObject } from "crypto";
|
||||||
|
|
||||||
import { TableName, TPkiSubscribers } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { decryptAppConnection } from "@app/services/app-connection/app-connection-fns";
|
||||||
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
||||||
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
||||||
import { TAwsConnection, TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types";
|
import { TAwsConnection, TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types";
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
CertStatus
|
CertStatus
|
||||||
} from "@app/services/certificate/certificate-types";
|
} from "@app/services/certificate/certificate-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 { 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";
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ import { keyAlgorithmToAlgCfg } from "../certificate-authority-fns";
|
|||||||
import { TCertificateAuthority } from "../certificate-authority-types";
|
import { TCertificateAuthority } from "../certificate-authority-types";
|
||||||
import { TExternalCertificateAuthorityDALFactory } from "../external-certificate-authority-dal";
|
import { TExternalCertificateAuthorityDALFactory } from "../external-certificate-authority-dal";
|
||||||
import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
|
import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
|
||||||
|
import { AcmeCertificateAuthorityCredentialsSchema } from "./acme-certificate-authority-schemas";
|
||||||
import {
|
import {
|
||||||
TAcmeCertificateAuthority,
|
TAcmeCertificateAuthority,
|
||||||
TCreateAcmeCertificateAuthorityDTO,
|
TCreateAcmeCertificateAuthorityDTO,
|
||||||
@@ -47,7 +50,11 @@ type TAcmeCertificateAuthorityFnsDeps = {
|
|||||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "encryptWithKmsKey" | "generateKmsKey">;
|
kmsService: Pick<
|
||||||
|
TKmsServiceFactory,
|
||||||
|
"encryptWithKmsKey" | "generateKmsKey" | "createCipherPairWithDataKey" | "decryptWithKmsKey"
|
||||||
|
>;
|
||||||
|
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findOne" | "updateById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findById" | "findOne" | "updateById" | "transaction">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +66,7 @@ type DBConfigurationColumn = {
|
|||||||
|
|
||||||
export const castDbEntryToAcmeCertificateAuthority = (
|
export const castDbEntryToAcmeCertificateAuthority = (
|
||||||
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>
|
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>
|
||||||
): TAcmeCertificateAuthority => {
|
): TAcmeCertificateAuthority & { credentials: unknown } => {
|
||||||
if (!ca.externalCa) {
|
if (!ca.externalCa) {
|
||||||
throw new BadRequestError({ message: "Malformed ACME certificate authority" });
|
throw new BadRequestError({ message: "Malformed ACME certificate authority" });
|
||||||
}
|
}
|
||||||
@@ -72,6 +79,7 @@ export const castDbEntryToAcmeCertificateAuthority = (
|
|||||||
disableDirectIssuance: ca.disableDirectIssuance,
|
disableDirectIssuance: ca.disableDirectIssuance,
|
||||||
name: ca.externalCa.name,
|
name: ca.externalCa.name,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
|
credentials: ca.externalCa.credentials,
|
||||||
configuration: {
|
configuration: {
|
||||||
dnsAppConnectionId: ca.externalCa.dnsAppConnectionId as string,
|
dnsAppConnectionId: ca.externalCa.dnsAppConnectionId as string,
|
||||||
dnsProvider: dbConfigurationCol.dnsProvider as AcmeDnsProvider,
|
dnsProvider: dbConfigurationCol.dnsProvider as AcmeDnsProvider,
|
||||||
@@ -147,7 +155,8 @@ export const AcmeCertificateAuthorityFns = ({
|
|||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
certificateSecretDAL,
|
certificateSecretDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
pkiSubscriberDAL
|
||||||
}: TAcmeCertificateAuthorityFnsDeps) => {
|
}: TAcmeCertificateAuthorityFnsDeps) => {
|
||||||
const createCertificateAuthority = async ({
|
const createCertificateAuthority = async ({
|
||||||
name,
|
name,
|
||||||
@@ -320,20 +329,66 @@ export const AcmeCertificateAuthorityFns = ({
|
|||||||
return cas.map(castDbEntryToAcmeCertificateAuthority);
|
return cas.map(castDbEntryToAcmeCertificateAuthority);
|
||||||
};
|
};
|
||||||
|
|
||||||
// SHEEN TODO: need to execute this from a job
|
const orderCertificate = async (subscriberId: string) => {
|
||||||
const orderCertificate = async (
|
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||||
subscriber: TPkiSubscribers,
|
if (!subscriber.caId) {
|
||||||
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>,
|
throw new BadRequestError({ message: "Subscriber does not have a CA" });
|
||||||
actor: OrgServiceActor
|
}
|
||||||
) => {
|
|
||||||
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId);
|
||||||
|
if (!ca.externalCa || ca.externalCa.type !== CaType.ACME) {
|
||||||
|
throw new BadRequestError({ message: "CA is not an ACME CA" });
|
||||||
|
}
|
||||||
|
|
||||||
const acmeCa = castDbEntryToAcmeCertificateAuthority(ca);
|
const acmeCa = castDbEntryToAcmeCertificateAuthority(ca);
|
||||||
|
|
||||||
// SHEEN TODO: need to save this in credentials field and reuse
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
const privateRsaKey = await acme.crypto.createPrivateRsaKey();
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
let accountKey: Buffer | undefined;
|
||||||
|
if (acmeCa.credentials) {
|
||||||
|
const decryptedCredentials = await kmsDecryptor({
|
||||||
|
cipherTextBlob: acmeCa.credentials as Buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
const parsedCredentials = await AcmeCertificateAuthorityCredentialsSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedCredentials.toString("utf8"))
|
||||||
|
);
|
||||||
|
|
||||||
|
accountKey = Buffer.from(parsedCredentials.accountKey, "base64");
|
||||||
|
}
|
||||||
|
if (!accountKey) {
|
||||||
|
accountKey = await acme.crypto.createPrivateRsaKey();
|
||||||
|
const newCredentials = {
|
||||||
|
accountKey: accountKey.toString("base64")
|
||||||
|
};
|
||||||
|
const { cipherTextBlob: encryptedNewCredentials } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(JSON.stringify(newCredentials))
|
||||||
|
});
|
||||||
|
await externalCertificateAuthorityDAL.update(
|
||||||
|
{
|
||||||
|
certificateAuthorityId: acmeCa.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
credentials: encryptedNewCredentials
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const acmeClient = new acme.Client({
|
const acmeClient = new acme.Client({
|
||||||
directoryUrl: acmeCa.configuration.directoryUrl,
|
directoryUrl: acmeCa.configuration.directoryUrl,
|
||||||
accountKey: privateRsaKey
|
accountKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||||
@@ -349,13 +404,8 @@ export const AcmeCertificateAuthorityFns = ({
|
|||||||
skLeaf
|
skLeaf
|
||||||
);
|
);
|
||||||
|
|
||||||
// SHEEN TODO: need to update this to remove dependence on ACTOR
|
|
||||||
const appConnection = await appConnectionDAL.findById(acmeCa.configuration.dnsAppConnectionId);
|
const appConnection = await appConnectionDAL.findById(acmeCa.configuration.dnsAppConnectionId);
|
||||||
const connection = await appConnectionService.connectAppConnectionById(
|
const connection = await decryptAppConnection(appConnection, kmsService);
|
||||||
appConnection.app as AppConnection,
|
|
||||||
acmeCa.configuration.dnsAppConnectionId,
|
|
||||||
actor
|
|
||||||
);
|
|
||||||
|
|
||||||
const pem = await acmeClient.auto({
|
const pem = await acmeClient.auto({
|
||||||
csr: certificateCsr,
|
csr: certificateCsr,
|
||||||
@@ -385,20 +435,9 @@ export const AcmeCertificateAuthorityFns = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("PEM IS", pem);
|
|
||||||
|
|
||||||
const [leafCert, parentCert] = acme.crypto.splitPemChain(pem);
|
const [leafCert, parentCert] = acme.crypto.splitPemChain(pem);
|
||||||
const certObj = new x509.X509Certificate(leafCert);
|
const certObj = new x509.X509Certificate(leafCert);
|
||||||
|
|
||||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
|
||||||
projectId: ca.projectId,
|
|
||||||
projectDAL,
|
|
||||||
kmsService
|
|
||||||
});
|
|
||||||
|
|
||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
|
||||||
kmsId: certificateManagerKmsId
|
|
||||||
});
|
|
||||||
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const AcmeCertificateAuthorityConfigurationSchema = z.object({
|
|||||||
accountEmail: z.string().trim()
|
accountEmail: z.string().trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const AcmeCertificateAuthorityCredentialsSchema = z.object({
|
||||||
|
accountKey: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const AcmeCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.ACME).extend({
|
export const AcmeCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.ACME).extend({
|
||||||
type: z.literal(CaType.ACME),
|
type: z.literal(CaType.ACME),
|
||||||
configuration: AcmeCertificateAuthorityConfigurationSchema
|
configuration: AcmeCertificateAuthorityConfigurationSchema
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
|
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
|
||||||
db.ref("status").withSchema(TableName.ExternalCertificateAuthority).as("externalStatus"),
|
db.ref("status").withSchema(TableName.ExternalCertificateAuthority).as("externalStatus"),
|
||||||
db.ref("configuration").withSchema(TableName.ExternalCertificateAuthority).as("externalConfiguration"),
|
db.ref("configuration").withSchema(TableName.ExternalCertificateAuthority).as("externalConfiguration"),
|
||||||
|
db.ref("credentials").withSchema(TableName.ExternalCertificateAuthority).as("externalCredentials"),
|
||||||
db
|
db
|
||||||
.ref("dnsAppConnectionId")
|
.ref("dnsAppConnectionId")
|
||||||
.withSchema(TableName.ExternalCertificateAuthority)
|
.withSchema(TableName.ExternalCertificateAuthority)
|
||||||
@@ -99,7 +100,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
status: result.externalStatus,
|
status: result.externalStatus,
|
||||||
configuration: result.externalConfiguration,
|
configuration: result.externalConfiguration,
|
||||||
dnsAppConnectionId: result.externalDnsAppConnectionId,
|
dnsAppConnectionId: result.externalDnsAppConnectionId,
|
||||||
appConnectionId: result.externalAppConnectionId
|
appConnectionId: result.externalAppConnectionId,
|
||||||
|
credentials: result.externalCredentials
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
@@ -195,6 +197,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
.ref("dnsAppConnectionId")
|
.ref("dnsAppConnectionId")
|
||||||
.withSchema(TableName.ExternalCertificateAuthority)
|
.withSchema(TableName.ExternalCertificateAuthority)
|
||||||
.as("externalDnsAppConnectionId"),
|
.as("externalDnsAppConnectionId"),
|
||||||
|
db.ref("credentials").withSchema(TableName.ExternalCertificateAuthority).as("externalCredentials"),
|
||||||
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -243,7 +246,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
status: ca.externalStatus,
|
status: ca.externalStatus,
|
||||||
configuration: ca.externalConfiguration,
|
configuration: ca.externalConfiguration,
|
||||||
dnsAppConnectionId: ca.externalDnsAppConnectionId,
|
dnsAppConnectionId: ca.externalDnsAppConnectionId,
|
||||||
appConnectionId: ca.externalAppConnectionId
|
appConnectionId: ca.externalAppConnectionId,
|
||||||
|
credentials: ca.externalCredentials
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
@@ -13,21 +14,42 @@ 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 { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||||
|
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
||||||
|
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
|
||||||
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
|
import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal";
|
||||||
|
import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
|
import { CaType } from "./certificate-authority-enums";
|
||||||
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 "./internal/internal-certificate-authority-types";
|
import { TExternalCertificateAuthorityDALFactory } from "./external-certificate-authority-dal";
|
||||||
|
import {
|
||||||
|
TOrderCertificateForSubscriberDTO,
|
||||||
|
TRotateCaCrlTriggerDTO
|
||||||
|
} from "./internal/internal-certificate-authority-types";
|
||||||
|
|
||||||
type TCertificateAuthorityQueueFactoryDep = {
|
type TCertificateAuthorityQueueFactoryDep = {
|
||||||
// TODO: Pick
|
|
||||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||||
|
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||||
|
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||||
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||||
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
|
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
|
||||||
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
|
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
|
||||||
certificateDAL: TCertificateDALFactory;
|
certificateDAL: TCertificateDALFactory;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
kmsService: Pick<
|
||||||
|
TKmsServiceFactory,
|
||||||
|
"generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey" | "createCipherPairWithDataKey"
|
||||||
|
>;
|
||||||
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
|
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
|
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
|
||||||
|
|
||||||
export const certificateAuthorityQueueFactory = ({
|
export const certificateAuthorityQueueFactory = ({
|
||||||
@@ -37,8 +59,28 @@ export const certificateAuthorityQueueFactory = ({
|
|||||||
certificateDAL,
|
certificateDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
queueService
|
queueService,
|
||||||
|
keyStore,
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService,
|
||||||
|
externalCertificateAuthorityDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
pkiSubscriberDAL
|
||||||
}: TCertificateAuthorityQueueFactoryDep) => {
|
}: TCertificateAuthorityQueueFactoryDep) => {
|
||||||
|
const acmeFns = AcmeCertificateAuthorityFns({
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
externalCertificateAuthorityDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
kmsService,
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
projectDAL
|
||||||
|
});
|
||||||
|
|
||||||
// TODO 1: auto-periodic rotation
|
// TODO 1: auto-periodic rotation
|
||||||
// TODO 2: manual rotation
|
// TODO 2: manual rotation
|
||||||
|
|
||||||
@@ -71,6 +113,51 @@ export const certificateAuthorityQueueFactory = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const orderCertificateForSubscriber = async ({ subscriberId, caType }: TOrderCertificateForSubscriberDTO) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.CaLifecycle,
|
||||||
|
QueueJobs.CaOrderCertificateForSubscriber,
|
||||||
|
{
|
||||||
|
subscriberId,
|
||||||
|
caType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attempts: 1,
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.CaLifecycle, async (job) => {
|
||||||
|
if (job.name === QueueJobs.CaOrderCertificateForSubscriber) {
|
||||||
|
const { subscriberId, caType } = job.data;
|
||||||
|
let lock: Awaited<ReturnType<typeof keyStore.acquireLock>>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
lock = await keyStore.acquireLock(
|
||||||
|
[KeyStorePrefixes.CaOrderCertificateForSubscriberLock(subscriberId)],
|
||||||
|
// scott: not sure on this duration; syncs can take excessive amounts of time so we need to keep it locked,
|
||||||
|
// but should always release below...
|
||||||
|
5 * 60 * 1000
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.info(`CaOrderCertificate Failed to acquire lock [subscriberId=${subscriberId}] [job=${job.name}]`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (caType === CaType.ACME) {
|
||||||
|
await acmeFns.orderCertificate(subscriberId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e, `CaOrderCertificate Failed [subscriberId=${subscriberId}] [job=${job.name}]`);
|
||||||
|
} finally {
|
||||||
|
await lock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
queueService.start(QueueName.CaCrlRotation, async (job) => {
|
queueService.start(QueueName.CaCrlRotation, async (job) => {
|
||||||
const { caId } = job.data;
|
const { caId } = job.data;
|
||||||
logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
|
logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
|
||||||
@@ -144,6 +231,7 @@ export const certificateAuthorityQueueFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setCaCrlRotationInterval
|
setCaCrlRotationInterval,
|
||||||
|
orderCertificateForSubscriber
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import { OrgServiceActor } from "@app/lib/types";
|
|||||||
|
|
||||||
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||||
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
||||||
|
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
|
||||||
|
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
||||||
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
|
import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
|
||||||
import {
|
import {
|
||||||
@@ -46,6 +51,14 @@ type TCertificateAuthorityServiceFactoryDep = {
|
|||||||
"findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId"
|
"findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId"
|
||||||
>;
|
>;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
||||||
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||||
|
kmsService: Pick<
|
||||||
|
TKmsServiceFactory,
|
||||||
|
"encryptWithKmsKey" | "generateKmsKey" | "createCipherPairWithDataKey" | "decryptWithKmsKey"
|
||||||
|
>;
|
||||||
|
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
|
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
|
||||||
@@ -57,13 +70,24 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
internalCertificateAuthorityService,
|
internalCertificateAuthorityService,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
externalCertificateAuthorityDAL
|
externalCertificateAuthorityDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
kmsService,
|
||||||
|
pkiSubscriberDAL
|
||||||
}: TCertificateAuthorityServiceFactoryDep) => {
|
}: TCertificateAuthorityServiceFactoryDep) => {
|
||||||
const acmeFns = AcmeCertificateAuthorityFns({
|
const acmeFns = AcmeCertificateAuthorityFns({
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
externalCertificateAuthorityDAL
|
externalCertificateAuthorityDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
kmsService,
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
projectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const createCertificateAuthority = async (
|
const createCertificateAuthority = async (
|
||||||
|
|||||||
+6
-1
@@ -9,7 +9,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
|
|
||||||
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority-cert-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal";
|
||||||
import { CaRenewalType, CaStatus, InternalCaType } from "../certificate-authority-enums";
|
import { CaRenewalType, CaStatus, CaType, InternalCaType } from "../certificate-authority-enums";
|
||||||
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
||||||
import {
|
import {
|
||||||
CreateInternalCertificateAuthoritySchema,
|
CreateInternalCertificateAuthoritySchema,
|
||||||
@@ -212,3 +212,8 @@ export type TRotateCaCrlTriggerDTO = {
|
|||||||
caId: string;
|
caId: string;
|
||||||
rotationIntervalDays: number;
|
rotationIntervalDays: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TOrderCertificateForSubscriberDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
caType: CaType;
|
||||||
|
};
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const pkiAlertDALFactory = (db: TDbClient) => {
|
|||||||
recipientEmails: string;
|
recipientEmails: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SHEEN TODO: FIX REGRESION HERE
|
||||||
// gets CAs and certificates as part of PKI collection items
|
// gets CAs and certificates as part of PKI collection items
|
||||||
const combinedQuery = db
|
const combinedQuery = db
|
||||||
.replicaNode()
|
.replicaNode()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { PkiSubscribersSchema } from "@app/db/schemas";
|
import { PkiSubscribersSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
||||||
@@ -11,4 +13,6 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
|||||||
ttl: true,
|
ttl: true,
|
||||||
keyUsages: true,
|
keyUsages: true,
|
||||||
extendedKeyUsages: true
|
extendedKeyUsages: true
|
||||||
|
}).extend({
|
||||||
|
supportsImmediateCertIssuance: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,11 +38,8 @@ import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subsc
|
|||||||
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 { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
|
||||||
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
|
||||||
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
import { AcmeCertificateAuthorityFns } from "../certificate-authority/acme/acme-certificate-authority-fns";
|
import { TCertificateAuthorityQueueFactory } from "../certificate-authority/certificate-authority-queue";
|
||||||
import { TExternalCertificateAuthorityDALFactory } from "../certificate-authority/external-certificate-authority-dal";
|
|
||||||
import { InternalCertificateAuthorityFns } from "../certificate-authority/internal/internal-certificate-authority-fns";
|
import { InternalCertificateAuthorityFns } from "../certificate-authority/internal/internal-certificate-authority-fns";
|
||||||
import {
|
import {
|
||||||
PkiSubscriberStatus,
|
PkiSubscriberStatus,
|
||||||
@@ -57,9 +54,6 @@ import {
|
|||||||
} from "./pki-subscriber-types";
|
} from "./pki-subscriber-types";
|
||||||
|
|
||||||
type TPkiSubscriberServiceFactoryDep = {
|
type TPkiSubscriberServiceFactoryDep = {
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">;
|
|
||||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
|
||||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
|
||||||
pkiSubscriberDAL: Pick<
|
pkiSubscriberDAL: Pick<
|
||||||
TPkiSubscriberDALFactory,
|
TPkiSubscriberDALFactory,
|
||||||
"create" | "findById" | "updateById" | "deleteById" | "transaction" | "find" | "findOne"
|
"create" | "findById" | "updateById" | "deleteById" | "transaction" | "find" | "findOne"
|
||||||
@@ -70,6 +64,7 @@ type TPkiSubscriberServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
|
certificateAuthorityQueue: Pick<TCertificateAuthorityQueueFactory, "orderCertificateForSubscriber">;
|
||||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction" | "countCertificatesForPkiSubscriber" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction" | "countCertificatesForPkiSubscriber" | "find">;
|
||||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||||
@@ -93,9 +88,7 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService,
|
permissionService,
|
||||||
appConnectionDAL,
|
certificateAuthorityQueue
|
||||||
appConnectionService,
|
|
||||||
externalCertificateAuthorityDAL
|
|
||||||
}: TPkiSubscriberServiceFactoryDep) => {
|
}: TPkiSubscriberServiceFactoryDep) => {
|
||||||
const internalCaFns = InternalCertificateAuthorityFns({
|
const internalCaFns = InternalCertificateAuthorityFns({
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
@@ -109,18 +102,6 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const acmeCaFns = AcmeCertificateAuthorityFns({
|
|
||||||
appConnectionDAL,
|
|
||||||
appConnectionService,
|
|
||||||
certificateAuthorityDAL,
|
|
||||||
externalCertificateAuthorityDAL,
|
|
||||||
certificateDAL,
|
|
||||||
certificateBodyDAL,
|
|
||||||
certificateSecretDAL,
|
|
||||||
kmsService,
|
|
||||||
projectDAL
|
|
||||||
});
|
|
||||||
|
|
||||||
const createSubscriber = async ({
|
const createSubscriber = async ({
|
||||||
name,
|
name,
|
||||||
commonName,
|
commonName,
|
||||||
@@ -198,7 +179,18 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return subscriber;
|
let supportsImmediateCertIssuance = false;
|
||||||
|
if (subscriber.caId) {
|
||||||
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId);
|
||||||
|
if (ca.internalCa?.id) {
|
||||||
|
supportsImmediateCertIssuance = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...subscriber,
|
||||||
|
supportsImmediateCertIssuance
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSubscriber = async ({
|
const updateSubscriber = async ({
|
||||||
@@ -325,16 +317,16 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
|
|
||||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId);
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId);
|
||||||
if (ca.internalCa?.id) {
|
if (ca.internalCa?.id) {
|
||||||
throw new BadRequestError({ message: "CA does not support ordering certificates" });
|
throw new BadRequestError({ message: "CA does not support ordering of certificates" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ca.externalCa?.id && ca.externalCa.type === CaType.ACME) {
|
if (ca.externalCa?.id && ca.externalCa.type === CaType.ACME) {
|
||||||
return acmeCaFns.orderCertificate(subscriber, ca, {
|
await certificateAuthorityQueue.orderCertificateForSubscriber({
|
||||||
type: actor,
|
subscriberId: subscriber.id,
|
||||||
id: actorId,
|
caType: ca.externalCa.type
|
||||||
authMethod: actorAuthMethod,
|
|
||||||
orgId: actorOrgId
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestError({ message: "Unsupported CA type" });
|
throw new BadRequestError({ message: "Unsupported CA type" });
|
||||||
@@ -380,15 +372,6 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
return internalCaFns.issueCertificate(subscriber, ca);
|
return internalCaFns.issueCertificate(subscriber, ca);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ca.externalCa?.id && ca.externalCa.type === CaType.ACME) {
|
|
||||||
return acmeCaFns.orderCertificate(subscriber, ca, {
|
|
||||||
type: actor,
|
|
||||||
id: actorId,
|
|
||||||
authMethod: actorAuthMethod,
|
|
||||||
orgId: actorOrgId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadRequestError({ message: "CA does not support immediate issuance of certificates" });
|
throw new BadRequestError({ message: "CA does not support immediate issuance of certificates" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export {
|
|||||||
useCreatePkiSubscriber,
|
useCreatePkiSubscriber,
|
||||||
useDeletePkiSubscriber,
|
useDeletePkiSubscriber,
|
||||||
useIssuePkiSubscriberCert,
|
useIssuePkiSubscriberCert,
|
||||||
|
useOrderPkiSubscriberCert,
|
||||||
useUpdatePkiSubscriber
|
useUpdatePkiSubscriber
|
||||||
} from "./mutations";
|
} from "./mutations";
|
||||||
export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries";
|
export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries";
|
||||||
|
|||||||
@@ -108,3 +108,17 @@ export const useIssuePkiSubscriberCert = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useOrderPkiSubscriberCert = () => {
|
||||||
|
return useMutation<{ message: string }, object, TIssuePkiSubscriberCertDTO>({
|
||||||
|
mutationFn: async ({ subscriberName, projectId }) => {
|
||||||
|
const { data } = await apiRequest.post(
|
||||||
|
`/api/v1/pki/subscribers/${subscriberName}/order-certificate`,
|
||||||
|
{
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type TPkiSubscriber = {
|
|||||||
subjectAlternativeNames: string[];
|
subjectAlternativeNames: string[];
|
||||||
keyUsages: CertKeyUsage[];
|
keyUsages: CertKeyUsage[];
|
||||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
supportsImmediateCertIssuance?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreatePkiSubscriberDTO = {
|
export type TCreatePkiSubscriberDTO = {
|
||||||
@@ -51,3 +52,8 @@ export type TIssuePkiSubscriberCertDTO = {
|
|||||||
subscriberName: string;
|
subscriberName: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TOrderPkiSubscriberCertDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
|||||||
+31
-14
@@ -13,7 +13,11 @@ import {
|
|||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { useTimedReset } from "@app/hooks";
|
import { useTimedReset } from "@app/hooks";
|
||||||
import { useGetPkiSubscriber, useIssuePkiSubscriberCert } from "@app/hooks/api";
|
import {
|
||||||
|
useGetPkiSubscriber,
|
||||||
|
useIssuePkiSubscriberCert,
|
||||||
|
useOrderPkiSubscriberCert
|
||||||
|
} from "@app/hooks/api";
|
||||||
import { pkiSubscriberStatusToNameMap } from "@app/hooks/api/pkiSubscriber/constants";
|
import { pkiSubscriberStatusToNameMap } from "@app/hooks/api/pkiSubscriber/constants";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -49,23 +53,34 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
|
|||||||
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
|
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
|
||||||
useIssuePkiSubscriberCert();
|
useIssuePkiSubscriberCert();
|
||||||
|
|
||||||
|
const { mutateAsync: orderPkiSubscriberCert } = useOrderPkiSubscriberCert();
|
||||||
|
|
||||||
const onIssuePkiSubscriberCert = async () => {
|
const onIssuePkiSubscriberCert = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await issuePkiSubscriberCert({ subscriberName, projectId });
|
if (pkiSubscriber?.supportsImmediateCertIssuance) {
|
||||||
|
const response = await issuePkiSubscriberCert({ subscriberName, projectId });
|
||||||
|
|
||||||
setCertificateDetails({
|
setCertificateDetails({
|
||||||
serialNumber: response.serialNumber,
|
serialNumber: response.serialNumber,
|
||||||
certificate: response.certificate,
|
certificate: response.certificate,
|
||||||
certificateChain: response.certificateChain,
|
certificateChain: response.certificateChain,
|
||||||
privateKey: response.privateKey
|
privateKey: response.privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully issued certificate",
|
text: "Successfully issued certificate",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await orderPkiSubscriberCert({ subscriberName, projectId });
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully ordered certificate. It will be issued after CA processing.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -158,7 +173,9 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
|
|||||||
onIssuePkiSubscriberCert();
|
onIssuePkiSubscriberCert();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Issue Certificate
|
{pkiSubscriber?.supportsImmediateCertIssuance
|
||||||
|
? "Issue Certificate"
|
||||||
|
: "Order Certificate"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user