mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
misc: addressed comments
This commit is contained in:
@@ -29,7 +29,6 @@ import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns
|
||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal";
|
||||
import { CaStatus, CaType } from "../certificate-authority-enums";
|
||||
import { keyAlgorithmToAlgCfg } from "../certificate-authority-fns";
|
||||
import { TCertificateAuthority } from "../certificate-authority-types";
|
||||
import { TExternalCertificateAuthorityDALFactory } from "../external-certificate-authority-dal";
|
||||
import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
|
||||
import { AcmeCertificateAuthorityCredentialsSchema } from "./acme-certificate-authority-schemas";
|
||||
@@ -62,12 +61,13 @@ type DBConfigurationColumn = {
|
||||
dnsProvider: string;
|
||||
directoryUrl: string;
|
||||
accountEmail: string;
|
||||
hostedZoneId: string;
|
||||
};
|
||||
|
||||
export const castDbEntryToAcmeCertificateAuthority = (
|
||||
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>
|
||||
): TAcmeCertificateAuthority & { credentials: unknown } => {
|
||||
if (!ca.externalCa) {
|
||||
if (!ca.externalCa?.id) {
|
||||
throw new BadRequestError({ message: "Malformed ACME certificate authority" });
|
||||
}
|
||||
|
||||
@@ -82,7 +82,10 @@ export const castDbEntryToAcmeCertificateAuthority = (
|
||||
credentials: ca.externalCa.credentials,
|
||||
configuration: {
|
||||
dnsAppConnectionId: ca.externalCa.dnsAppConnectionId as string,
|
||||
dnsProvider: dbConfigurationCol.dnsProvider as AcmeDnsProvider,
|
||||
dnsProviderConfig: {
|
||||
provider: dbConfigurationCol.dnsProvider as AcmeDnsProvider,
|
||||
hostedZoneId: dbConfigurationCol.hostedZoneId
|
||||
},
|
||||
directoryUrl: dbConfigurationCol.directoryUrl,
|
||||
accountEmail: dbConfigurationCol.accountEmail
|
||||
},
|
||||
@@ -90,7 +93,12 @@ export const castDbEntryToAcmeCertificateAuthority = (
|
||||
};
|
||||
};
|
||||
|
||||
export const route53InsertTxtRecord = async (connection: TAwsConnectionConfig, domain: string, value: string) => {
|
||||
export const route53InsertTxtRecord = async (
|
||||
connection: TAwsConnectionConfig,
|
||||
hostedZoneId: string,
|
||||
domain: string,
|
||||
value: string
|
||||
) => {
|
||||
const config = await getAwsConnectionConfig(connection, AWSRegion.US_WEST_1); // REGION is irrelevant because Route53 is global
|
||||
const route53Client = new Route53Client({
|
||||
credentials: config.credentials!,
|
||||
@@ -98,7 +106,7 @@ export const route53InsertTxtRecord = async (connection: TAwsConnectionConfig, d
|
||||
});
|
||||
|
||||
const command = new ChangeResourceRecordSetsCommand({
|
||||
HostedZoneId: "Z040441124N1GOOMCQYX1", // SHEEN TODO: Get this from user input
|
||||
HostedZoneId: hostedZoneId,
|
||||
ChangeBatch: {
|
||||
Comment: "Set ACME challenge TXT record",
|
||||
Changes: [
|
||||
@@ -118,7 +126,12 @@ export const route53InsertTxtRecord = async (connection: TAwsConnectionConfig, d
|
||||
await route53Client.send(command);
|
||||
};
|
||||
|
||||
export const route53DeleteTxtRecord = async (connection: TAwsConnectionConfig, domain: string, value: string) => {
|
||||
export const route53DeleteTxtRecord = async (
|
||||
connection: TAwsConnectionConfig,
|
||||
hostedZoneId: string,
|
||||
domain: string,
|
||||
value: string
|
||||
) => {
|
||||
const config = await getAwsConnectionConfig(connection, AWSRegion.US_WEST_1); // REGION is irrelevant because Route53 is global
|
||||
const route53Client = new Route53Client({
|
||||
credentials: config.credentials!,
|
||||
@@ -126,7 +139,7 @@ export const route53DeleteTxtRecord = async (connection: TAwsConnectionConfig, d
|
||||
});
|
||||
|
||||
const command = new ChangeResourceRecordSetsCommand({
|
||||
HostedZoneId: "Z040441124N1GOOMCQYX1", // SHEEN TODO: same here
|
||||
HostedZoneId: hostedZoneId,
|
||||
ChangeBatch: {
|
||||
Comment: "Delete ACME challenge TXT record",
|
||||
Changes: [
|
||||
@@ -173,14 +186,14 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
disableDirectIssuance: boolean;
|
||||
actor: OrgServiceActor;
|
||||
}) => {
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProvider } = configuration;
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig } = configuration;
|
||||
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
|
||||
|
||||
if (!appConnection) {
|
||||
throw new NotFoundError({ message: `App connection with ID '${dnsAppConnectionId}' not found` });
|
||||
}
|
||||
|
||||
if (dnsProvider === AcmeDnsProvider.Route53 && appConnection.app !== AppConnection.AWS) {
|
||||
if (dnsProviderConfig.provider === AcmeDnsProvider.Route53 && appConnection.app !== AppConnection.AWS) {
|
||||
throw new BadRequestError({
|
||||
message: `App connection with ID '${dnsAppConnectionId}' is not an AWS connection`
|
||||
});
|
||||
@@ -207,7 +220,8 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
configuration: {
|
||||
directoryUrl,
|
||||
accountEmail,
|
||||
dnsProvider
|
||||
dnsProvider: dnsProviderConfig.provider,
|
||||
hostedZoneId: dnsProviderConfig.hostedZoneId
|
||||
},
|
||||
status
|
||||
},
|
||||
@@ -217,19 +231,11 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
return certificateAuthorityDAL.findByIdWithAssociatedCa(ca.id, tx);
|
||||
});
|
||||
|
||||
if (!caEntity.externalCa) {
|
||||
if (!caEntity.externalCa?.id) {
|
||||
throw new BadRequestError({ message: "Failed to create external certificate authority" });
|
||||
}
|
||||
|
||||
return {
|
||||
id: caEntity.id,
|
||||
type: CaType.ACME,
|
||||
disableDirectIssuance: caEntity.disableDirectIssuance,
|
||||
name: caEntity.externalCa.name,
|
||||
projectId,
|
||||
status,
|
||||
configuration: caEntity.externalCa.configuration
|
||||
} as TCertificateAuthority;
|
||||
return castDbEntryToAcmeCertificateAuthority(caEntity);
|
||||
};
|
||||
|
||||
const updateCertificateAuthority = async ({
|
||||
@@ -237,24 +243,26 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
status,
|
||||
configuration,
|
||||
disableDirectIssuance,
|
||||
actor
|
||||
actor,
|
||||
name
|
||||
}: {
|
||||
id: string;
|
||||
status?: CaStatus;
|
||||
configuration: TUpdateAcmeCertificateAuthorityDTO["configuration"];
|
||||
disableDirectIssuance?: boolean;
|
||||
actor: OrgServiceActor;
|
||||
name?: string;
|
||||
}) => {
|
||||
const updatedCa = await certificateAuthorityDAL.transaction(async (tx) => {
|
||||
if (configuration) {
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProvider } = configuration;
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig } = configuration;
|
||||
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
|
||||
|
||||
if (!appConnection) {
|
||||
throw new NotFoundError({ message: `App connection with ID '${dnsAppConnectionId}' not found` });
|
||||
}
|
||||
|
||||
if (dnsProvider === AcmeDnsProvider.Route53 && appConnection.app !== AppConnection.AWS) {
|
||||
if (dnsProviderConfig.provider === AcmeDnsProvider.Route53 && appConnection.app !== AppConnection.AWS) {
|
||||
throw new BadRequestError({
|
||||
message: `App connection with ID '${dnsAppConnectionId}' is not an AWS connection`
|
||||
});
|
||||
@@ -273,24 +281,29 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
type: CaType.ACME
|
||||
},
|
||||
{
|
||||
configuration: { directoryUrl, accountEmail, dnsProvider, dnsAppConnectionId }
|
||||
dnsAppConnectionId,
|
||||
configuration: {
|
||||
directoryUrl,
|
||||
accountEmail,
|
||||
dnsProvider: dnsProviderConfig.provider,
|
||||
hostedZoneId: dnsProviderConfig.hostedZoneId
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
await externalCertificateAuthorityDAL.update(
|
||||
{
|
||||
certificateAuthorityId: id,
|
||||
type: CaType.ACME
|
||||
},
|
||||
{
|
||||
status
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
await externalCertificateAuthorityDAL.update(
|
||||
{
|
||||
certificateAuthorityId: id,
|
||||
type: CaType.ACME
|
||||
},
|
||||
{
|
||||
name,
|
||||
status
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (disableDirectIssuance !== undefined) {
|
||||
await certificateAuthorityDAL.updateById(
|
||||
@@ -305,19 +318,11 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
return certificateAuthorityDAL.findByIdWithAssociatedCa(id, tx);
|
||||
});
|
||||
|
||||
if (!updatedCa.externalCa) {
|
||||
if (!updatedCa.externalCa?.id) {
|
||||
throw new BadRequestError({ message: "Failed to update external certificate authority" });
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedCa.id,
|
||||
type: CaType.ACME,
|
||||
disableDirectIssuance: updatedCa.disableDirectIssuance,
|
||||
name: updatedCa.externalCa.name,
|
||||
projectId: updatedCa.projectId,
|
||||
status: updatedCa.externalCa.status,
|
||||
configuration: updatedCa.externalCa.configuration
|
||||
};
|
||||
return castDbEntryToAcmeCertificateAuthority(updatedCa);
|
||||
};
|
||||
|
||||
const listCertificateAuthorities = async ({ projectId }: { projectId: string }) => {
|
||||
@@ -329,7 +334,7 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
return cas.map(castDbEntryToAcmeCertificateAuthority);
|
||||
};
|
||||
|
||||
const orderCertificate = async (subscriberId: string) => {
|
||||
const orderSubscriberCertificate = async (subscriberId: string) => {
|
||||
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||
if (!subscriber.caId) {
|
||||
throw new BadRequestError({ message: "Subscriber does not have a CA" });
|
||||
@@ -341,6 +346,9 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
}
|
||||
|
||||
const acmeCa = castDbEntryToAcmeCertificateAuthority(ca);
|
||||
if (acmeCa.status !== CaStatus.ACTIVE) {
|
||||
throw new BadRequestError({ message: "CA is disabled" });
|
||||
}
|
||||
|
||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||
projectId: ca.projectId,
|
||||
@@ -421,16 +429,26 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
const recordName = `_acme-challenge.${authz.identifier.value}`; // e.g., "_acme-challenge.example.com"
|
||||
const recordValue = `"${keyAuthorization}"`; // must be double quoted
|
||||
|
||||
if (acmeCa.configuration.dnsProvider === AcmeDnsProvider.Route53) {
|
||||
await route53InsertTxtRecord(connection as TAwsConnection, recordName, recordValue);
|
||||
if (acmeCa.configuration.dnsProviderConfig.provider === AcmeDnsProvider.Route53) {
|
||||
await route53InsertTxtRecord(
|
||||
connection as TAwsConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
}
|
||||
},
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
|
||||
const recordName = `_acme-challenge.${authz.identifier.value}`; // e.g., "_acme-challenge.example.com"
|
||||
const recordValue = `"${keyAuthorization}"`; // must be double quoted
|
||||
|
||||
if (acmeCa.configuration.dnsProvider === AcmeDnsProvider.Route53) {
|
||||
await route53DeleteTxtRecord(connection as TAwsConnection, recordName, recordValue);
|
||||
if (acmeCa.configuration.dnsProviderConfig.provider === AcmeDnsProvider.Route53) {
|
||||
await route53DeleteTxtRecord(
|
||||
connection as TAwsConnection,
|
||||
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
|
||||
recordName,
|
||||
recordValue
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -466,7 +484,7 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
notAfter: certObj.notAfter,
|
||||
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[],
|
||||
caCertId: "s" // SHEEN TODO: merge Andrey's PR and then remove this
|
||||
projectId: ca.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -494,6 +512,6 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
createCertificateAuthority,
|
||||
updateCertificateAuthority,
|
||||
listCertificateAuthorities,
|
||||
orderCertificate
|
||||
orderSubscriberCertificate
|
||||
};
|
||||
};
|
||||
|
||||
+7
-3
@@ -10,9 +10,13 @@ import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
|
||||
|
||||
export const AcmeCertificateAuthorityConfigurationSchema = z.object({
|
||||
dnsAppConnectionId: z.string().trim(),
|
||||
dnsProvider: z.nativeEnum(AcmeDnsProvider),
|
||||
directoryUrl: z.string().trim(),
|
||||
accountEmail: z.string().trim()
|
||||
// soon, differentiate via the provider property
|
||||
dnsProviderConfig: z.object({
|
||||
provider: z.nativeEnum(AcmeDnsProvider),
|
||||
hostedZoneId: z.string().trim().min(1)
|
||||
}),
|
||||
directoryUrl: z.string().trim().min(1),
|
||||
accountEmail: z.string().trim().min(1)
|
||||
});
|
||||
|
||||
export const AcmeCertificateAuthorityCredentialsSchema = z.object({
|
||||
|
||||
@@ -114,7 +114,7 @@ export const getCaCredentials = async ({
|
||||
kmsService
|
||||
}: TGetCaCredentialsDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||
if (!ca?.internalCa) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||
if (!ca?.internalCa?.id) throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });
|
||||
|
||||
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId });
|
||||
if (!caSecret) throw new NotFoundError({ message: `CA secret for CA with ID '${caId}' not found` });
|
||||
@@ -257,7 +257,7 @@ export const rebuildCaCrl = async ({
|
||||
kmsService
|
||||
}: TRebuildCaCrlDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||
if (!ca?.internalCa) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||
if (!ca?.internalCa?.id) throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });
|
||||
|
||||
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import crypto from "crypto";
|
||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||
import { NotFoundError } from "@app/lib/errors";
|
||||
import { BadRequestError, NotFoundError } 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";
|
||||
@@ -114,6 +114,11 @@ export const certificateAuthorityQueueFactory = ({
|
||||
};
|
||||
|
||||
const orderCertificateForSubscriber = async ({ subscriberId, caType }: TOrderCertificateForSubscriberDTO) => {
|
||||
const entry = await keyStore.getItem(KeyStorePrefixes.CaOrderCertificateForSubscriberLock(subscriberId));
|
||||
if (entry) {
|
||||
throw new BadRequestError({ message: `Certificate order already in progress for subscriber ${subscriberId}` });
|
||||
}
|
||||
|
||||
await queueService.queue(
|
||||
QueueName.CaLifecycle,
|
||||
QueueJobs.CaOrderCertificateForSubscriber,
|
||||
@@ -137,8 +142,6 @@ export const certificateAuthorityQueueFactory = ({
|
||||
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) {
|
||||
@@ -148,7 +151,7 @@ export const certificateAuthorityQueueFactory = ({
|
||||
|
||||
try {
|
||||
if (caType === CaType.ACME) {
|
||||
await acmeFns.orderCertificate(subscriberId);
|
||||
await acmeFns.orderSubscriberCertificate(subscriberId);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e, `CaOrderCertificate Failed [subscriberId=${subscriberId}] [job=${job.name}]`);
|
||||
|
||||
@@ -14,7 +14,10 @@ import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
|
||||
import {
|
||||
AcmeCertificateAuthorityFns,
|
||||
castDbEntryToAcmeCertificateAuthority
|
||||
} from "./acme/acme-certificate-authority-fns";
|
||||
import {
|
||||
TCreateAcmeCertificateAuthorityDTO,
|
||||
TUpdateAcmeCertificateAuthorityDTO
|
||||
@@ -153,6 +156,8 @@ export const certificateAuthorityServiceFactory = ({
|
||||
actor
|
||||
});
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
||||
};
|
||||
|
||||
const findCertificateAuthorityById = async (
|
||||
@@ -181,9 +186,9 @@ export const certificateAuthorityServiceFactory = ({
|
||||
);
|
||||
|
||||
if (type === CaType.INTERNAL) {
|
||||
if (!certificateAuthority.internalCa) {
|
||||
if (!certificateAuthority.internalCa?.id) {
|
||||
throw new NotFoundError({
|
||||
message: `Could not find internal certificate authority with ID "${certificateAuthorityId}"`
|
||||
message: `Internal certificate authority with ID "${certificateAuthorityId}" not found`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,14 +209,11 @@ export const certificateAuthorityServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: certificateAuthority.id,
|
||||
type,
|
||||
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
||||
name: certificateAuthority.externalCa.name,
|
||||
projectId: certificateAuthority.projectId,
|
||||
configuration: certificateAuthority.externalCa.configuration
|
||||
} as TCertificateAuthority;
|
||||
if (type === CaType.ACME) {
|
||||
return castDbEntryToAcmeCertificateAuthority(certificateAuthority);
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
||||
};
|
||||
|
||||
const listCertificateAuthoritiesByProjectId = async (
|
||||
@@ -264,10 +266,12 @@ export const certificateAuthorityServiceFactory = ({
|
||||
if (type === CaType.ACME) {
|
||||
return acmeFns.listCertificateAuthorities({ projectId: finalProjectId });
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
||||
};
|
||||
|
||||
const updateCertificateAuthority = async (
|
||||
{ id, type, configuration, disableDirectIssuance, status }: TUpdateCertificateAuthorityDTO,
|
||||
{ id, type, configuration, disableDirectIssuance, status, name }: TUpdateCertificateAuthorityDTO,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(id);
|
||||
@@ -292,9 +296,9 @@ export const certificateAuthorityServiceFactory = ({
|
||||
);
|
||||
|
||||
if (type === CaType.INTERNAL) {
|
||||
if (!certificateAuthority.internalCa) {
|
||||
if (!certificateAuthority.internalCa?.id) {
|
||||
throw new NotFoundError({
|
||||
message: `Could not find internal certificate authority with ID "${id}"`
|
||||
message: `Internal certificate authority with ID "${id}" not found`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -328,7 +332,8 @@ export const certificateAuthorityServiceFactory = ({
|
||||
configuration: configuration as TUpdateAcmeCertificateAuthorityDTO["configuration"],
|
||||
disableDirectIssuance,
|
||||
actor,
|
||||
status
|
||||
status,
|
||||
name
|
||||
});
|
||||
}
|
||||
|
||||
@@ -357,15 +362,15 @@ export const certificateAuthorityServiceFactory = ({
|
||||
ProjectPermissionSub.CertificateAuthorities
|
||||
);
|
||||
|
||||
if (!certificateAuthority.internalCa && type === CaType.INTERNAL) {
|
||||
if (!certificateAuthority.internalCa?.id && type === CaType.INTERNAL) {
|
||||
throw new BadRequestError({
|
||||
message: "Certificate authority cannot be deleted due to mismatching type"
|
||||
message: "Internal certificate authority cannot be deleted"
|
||||
});
|
||||
}
|
||||
|
||||
if (certificateAuthority.externalCa && certificateAuthority.externalCa.type !== type) {
|
||||
if (certificateAuthority.externalCa?.id && certificateAuthority.externalCa.type !== type) {
|
||||
throw new BadRequestError({
|
||||
message: "Certificate authority cannot be deleted due to mismatching type"
|
||||
message: "External certificate authority cannot be deleted"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -383,15 +388,11 @@ export const certificateAuthorityServiceFactory = ({
|
||||
} as TCertificateAuthority;
|
||||
}
|
||||
|
||||
return {
|
||||
id: certificateAuthority.id,
|
||||
type,
|
||||
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
||||
name: certificateAuthority.externalCa?.name,
|
||||
projectId: certificateAuthority.projectId,
|
||||
configuration: certificateAuthority.externalCa?.configuration,
|
||||
status: certificateAuthority.externalCa?.status
|
||||
} as TCertificateAuthority;
|
||||
if (type === CaType.ACME) {
|
||||
return castDbEntryToAcmeCertificateAuthority(certificateAuthority);
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,9 +9,7 @@ export type TCertificateAuthority = TInternalCertificateAuthority | TAcmeCertifi
|
||||
|
||||
export type TCertificateAuthorityInput = TInternalCertificateAuthorityInput | TAcmeCertificateAuthorityInput;
|
||||
|
||||
export type TCreateCertificateAuthorityDTO = Omit<TCertificateAuthority, "type" | "id"> & {
|
||||
type: CaType;
|
||||
};
|
||||
export type TCreateCertificateAuthorityDTO = Omit<TCertificateAuthority, "id">;
|
||||
|
||||
export type TUpdateCertificateAuthorityDTO = Partial<Omit<TCreateCertificateAuthorityDTO, "projectId">> & {
|
||||
type: CaType;
|
||||
|
||||
+2
-1
@@ -225,7 +225,8 @@ export const InternalCertificateAuthorityFns = ({
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate,
|
||||
keyUsages: selectedKeyUsages,
|
||||
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[],
|
||||
projectId: ca.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
+26
-17
@@ -1209,8 +1209,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(certificateTemplate.caId);
|
||||
}
|
||||
|
||||
if (!ca?.internalCa) {
|
||||
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||
if (!ca?.internalCa?.id) {
|
||||
throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
@@ -1475,7 +1475,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate,
|
||||
keyUsages: selectedKeyUsages,
|
||||
extendedKeyUsages: selectedExtendedKeyUsages
|
||||
extendedKeyUsages: selectedExtendedKeyUsages,
|
||||
projectId: ca.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -1560,8 +1561,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(certificateTemplate.caId);
|
||||
}
|
||||
|
||||
if (!ca?.internalCa) {
|
||||
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||
if (!ca?.internalCa?.id) {
|
||||
throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });
|
||||
}
|
||||
|
||||
if (!dto.isInternal) {
|
||||
@@ -1854,6 +1855,20 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||
});
|
||||
|
||||
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||
caCertId: ca.internalCa.activeCaCertId,
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCertDAL,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
const certificateChainPem = `${issuingCaCertificate}\n${caCertChain}`.trim();
|
||||
|
||||
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||
plainText: Buffer.from(certificateChainPem)
|
||||
});
|
||||
|
||||
await certificateDAL.transaction(async (tx) => {
|
||||
const cert = await certificateDAL.create(
|
||||
{
|
||||
@@ -1868,7 +1883,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate,
|
||||
keyUsages: selectedKeyUsages,
|
||||
extendedKeyUsages: selectedExtendedKeyUsages
|
||||
extendedKeyUsages: selectedExtendedKeyUsages,
|
||||
projectId: ca.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -1876,7 +1892,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
await certificateBodyDAL.create(
|
||||
{
|
||||
certId: cert.id,
|
||||
encryptedCertificate
|
||||
encryptedCertificate,
|
||||
encryptedCertificateChain
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -1894,17 +1911,9 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
return cert;
|
||||
});
|
||||
|
||||
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||
caCertId: ca.internalCa.activeCaCertId,
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCertDAL,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
return {
|
||||
certificate: leafCert,
|
||||
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
|
||||
certificateChain: certificateChainPem,
|
||||
issuingCaCertificate,
|
||||
serialNumber,
|
||||
ca: expandInternalCa(ca),
|
||||
@@ -1923,7 +1932,7 @@ export const internalCertificateAuthorityServiceFactory = ({
|
||||
actorOrgId
|
||||
}: TGetCaCertificateTemplatesDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||
if (!ca?.internalCa) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||
if (!ca?.internalCa?.id) throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import * as x509 from "@peculiar/x509";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
@@ -52,8 +53,11 @@ export const constructPemChainFromCerts = (certificates: x509.X509Certificate[])
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
export const splitPemChain = (pemText: string) =>
|
||||
pemText.match(/-----BEGIN CERTIFICATE-----[^-]+-----END CERTIFICATE-----/g) || [];
|
||||
export const splitPemChain = (pemText: string) => {
|
||||
const re2Pattern = new RE2("-----BEGIN CERTIFICATE-----[^-]+-----END CERTIFICATE-----", "g");
|
||||
|
||||
return re2Pattern.match(pemText) || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the public and private key of certificate
|
||||
|
||||
@@ -22,7 +22,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
import { expandInternalCa, getCaCertChain, rebuildCaCrl } from "../certificate-authority/certificate-authority-fns";
|
||||
import { buildCertificateChain, getCertificateCredentials, revocationReasonToCrlCode } from "./certificate-fns";
|
||||
import { getCertificateCredentials, revocationReasonToCrlCode, splitPemChain } from "./certificate-fns";
|
||||
import { TCertificateSecretDALFactory } from "./certificate-secret-dal";
|
||||
import {
|
||||
CertExtendedKeyUsage,
|
||||
@@ -39,9 +39,9 @@ import {
|
||||
} from "./certificate-types";
|
||||
|
||||
type TCertificateServiceFactoryDep = {
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne">;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find" | "transaction" | "create">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne" | "create">;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne" | "create">;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById" | "findByIdWithAssociatedCa">;
|
||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
|
||||
@@ -77,7 +77,6 @@ export const certificateServiceFactory = ({
|
||||
*/
|
||||
const getCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -94,8 +93,7 @@ export const certificateServiceFactory = ({
|
||||
);
|
||||
|
||||
return {
|
||||
cert,
|
||||
ca: expandInternalCa(ca)
|
||||
cert
|
||||
};
|
||||
};
|
||||
|
||||
@@ -110,7 +108,6 @@ export const certificateServiceFactory = ({
|
||||
actorOrgId
|
||||
}: TGetCertPrivateKeyDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -135,7 +132,6 @@ export const certificateServiceFactory = ({
|
||||
});
|
||||
|
||||
return {
|
||||
ca: expandInternalCa(ca),
|
||||
cert,
|
||||
certPrivateKey
|
||||
};
|
||||
@@ -146,7 +142,6 @@ export const certificateServiceFactory = ({
|
||||
*/
|
||||
const deleteCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -165,8 +160,7 @@ export const certificateServiceFactory = ({
|
||||
const deletedCert = await certificateDAL.deleteById(cert.id);
|
||||
|
||||
return {
|
||||
deletedCert,
|
||||
ca: expandInternalCa(ca)
|
||||
deletedCert
|
||||
};
|
||||
};
|
||||
|
||||
@@ -184,8 +178,21 @@ export const certificateServiceFactory = ({
|
||||
actorOrgId
|
||||
}: TRevokeCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
|
||||
if (!cert.caId) {
|
||||
throw new BadRequestError({
|
||||
message: "Cannot revoke imported certificates"
|
||||
});
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(cert.caId);
|
||||
|
||||
if (ca.externalCa?.id) {
|
||||
throw new BadRequestError({
|
||||
message: "Cannot revoke external certificates"
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
@@ -234,7 +241,6 @@ export const certificateServiceFactory = ({
|
||||
*/
|
||||
const getCertBody = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertBodyDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -292,8 +298,234 @@ export const certificateServiceFactory = ({
|
||||
certificate: certObj.toString("pem"),
|
||||
certificateChain,
|
||||
serialNumber: certObj.serialNumber,
|
||||
cert,
|
||||
ca: expandInternalCa(ca)
|
||||
cert
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Import certificate
|
||||
*/
|
||||
const importCert = async ({
|
||||
projectSlug,
|
||||
pkiCollectionId,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId,
|
||||
friendlyName,
|
||||
certificatePem,
|
||||
chainPem,
|
||||
privateKeyPem
|
||||
}: TImportCertDTO) => {
|
||||
const collectionId = pkiCollectionId;
|
||||
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
let projectId = project.id;
|
||||
|
||||
const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId(
|
||||
projectId,
|
||||
ProjectType.CertificateManager
|
||||
);
|
||||
if (certManagerProjectFromSplit) {
|
||||
projectId = certManagerProjectFromSplit.id;
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.CertificateManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionCertificateActions.Create,
|
||||
ProjectPermissionSub.Certificates
|
||||
);
|
||||
|
||||
// Check PKI collection
|
||||
if (collectionId) {
|
||||
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
|
||||
if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" });
|
||||
if (pkiCollection.projectId !== projectId) throw new BadRequestError({ message: "Invalid PKI collection" });
|
||||
}
|
||||
|
||||
const leafCert = new x509.X509Certificate(certificatePem);
|
||||
|
||||
// Verify the certificate chain
|
||||
const chainCerts = splitPemChain(chainPem).map((pem) => new x509.X509Certificate(pem));
|
||||
|
||||
// Remove leaf cert from the chain if it's present
|
||||
if (chainCerts[0].equal(leafCert)) {
|
||||
chainCerts.splice(0, 1);
|
||||
}
|
||||
|
||||
if (chainCerts.length === 0) {
|
||||
throw new BadRequestError({
|
||||
message: "Certificate chain must contain at least one issuer certificate"
|
||||
});
|
||||
}
|
||||
|
||||
// Verify leaf certificate is signed by the first certificate in the chain
|
||||
const isLeafVerified = await leafCert.verify({ publicKey: chainCerts[0].publicKey }).catch(() => false);
|
||||
if (!isLeafVerified) {
|
||||
throw new BadRequestError({ message: "Leaf certificate verification against chain failed" });
|
||||
}
|
||||
|
||||
// Verify the entire chain of trust
|
||||
const verificationPromises = chainCerts.slice(0, -1).map(async (currentCert, index) => {
|
||||
const issuerCert = chainCerts[index + 1];
|
||||
return currentCert.verify({ publicKey: issuerCert.publicKey }).catch(() => false);
|
||||
});
|
||||
|
||||
const verificationResults = await Promise.all(verificationPromises);
|
||||
|
||||
if (verificationResults.some((result) => !result)) {
|
||||
throw new BadRequestError({
|
||||
message: "Certificate chain verification failed: broken trust chain"
|
||||
});
|
||||
}
|
||||
|
||||
// Verify private key matches the certificate
|
||||
let privateKey;
|
||||
try {
|
||||
privateKey = createPrivateKey(privateKeyPem);
|
||||
} catch (err) {
|
||||
throw new BadRequestError({ message: "Invalid private key format" });
|
||||
}
|
||||
|
||||
try {
|
||||
const message = Buffer.from(Buffer.alloc(32));
|
||||
const publicKey = createPublicKey(certificatePem);
|
||||
const signature = sign(null, message, privateKey);
|
||||
const isValid = verify(null, message, publicKey, signature);
|
||||
|
||||
if (!isValid) {
|
||||
throw new BadRequestError({ message: "Private key does not match certificate" });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestError) {
|
||||
throw err;
|
||||
}
|
||||
throw new BadRequestError({ message: "Error verifying private key against certificate" });
|
||||
}
|
||||
|
||||
// Get certificate attributes
|
||||
const commonName = Array.from(leafCert.subjectName.getField("CN")?.values() || [])[0] || "";
|
||||
|
||||
let altNames: undefined | string;
|
||||
const sanExtension = leafCert.extensions.find((ext) => ext.type === "2.5.29.17");
|
||||
if (sanExtension) {
|
||||
const sanNames = new x509.GeneralNames(sanExtension.value);
|
||||
altNames = sanNames.items.map((name) => name.value).join(", ");
|
||||
}
|
||||
|
||||
const { serialNumber, notBefore, notAfter } = leafCert;
|
||||
|
||||
// Encrypt certificate for storage
|
||||
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||
projectId,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||
kmsId: certificateManagerKeyId
|
||||
});
|
||||
|
||||
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||
plainText: Buffer.from(certificatePem)
|
||||
});
|
||||
|
||||
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
|
||||
plainText: Buffer.from(privateKeyPem)
|
||||
});
|
||||
|
||||
// Extract Key Usage
|
||||
const keyUsagesExt = leafCert.getExtension("2.5.29.15") as x509.KeyUsagesExtension;
|
||||
|
||||
let keyUsages: CertKeyUsage[] = [];
|
||||
if (keyUsagesExt) {
|
||||
keyUsages = Object.values(CertKeyUsage).filter(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
(keyUsage) => (x509.KeyUsageFlags[keyUsage] & keyUsagesExt.usages) !== 0
|
||||
);
|
||||
}
|
||||
|
||||
// Extract Extended Key Usage
|
||||
const extKeyUsageExt = leafCert.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension;
|
||||
let extendedKeyUsages: CertExtendedKeyUsage[] = [];
|
||||
if (extKeyUsageExt) {
|
||||
extendedKeyUsages = extKeyUsageExt.usages.map((ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string]);
|
||||
}
|
||||
|
||||
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||
plainText: Buffer.from(chainPem)
|
||||
});
|
||||
|
||||
const cert = await certificateDAL.transaction(async (tx) => {
|
||||
try {
|
||||
const txCert = await certificateDAL.create(
|
||||
{
|
||||
status: CertStatus.ACTIVE,
|
||||
friendlyName: friendlyName || commonName,
|
||||
commonName,
|
||||
altNames,
|
||||
serialNumber,
|
||||
notBefore,
|
||||
notAfter,
|
||||
projectId,
|
||||
keyUsages,
|
||||
extendedKeyUsages
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await certificateBodyDAL.create(
|
||||
{
|
||||
certId: txCert.id,
|
||||
encryptedCertificate,
|
||||
encryptedCertificateChain
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await certificateSecretDAL.create(
|
||||
{
|
||||
certId: txCert.id,
|
||||
encryptedPrivateKey
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (collectionId) {
|
||||
await pkiCollectionItemDAL.create(
|
||||
{
|
||||
pkiCollectionId: collectionId,
|
||||
certId: txCert.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
return txCert;
|
||||
} catch (error) {
|
||||
// @ts-expect-error We're expecting a database error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (error?.error?.code === "23505") {
|
||||
throw new BadRequestError({ message: "Certificate serial already exists in your project" });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
certificate: certificatePem,
|
||||
certificateChain: chainPem,
|
||||
privateKey: privateKeyPem,
|
||||
serialNumber,
|
||||
cert
|
||||
};
|
||||
};
|
||||
|
||||
@@ -303,7 +535,6 @@ export const certificateServiceFactory = ({
|
||||
*/
|
||||
const getCertBundle = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertBundleDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -375,8 +606,7 @@ export const certificateServiceFactory = ({
|
||||
certificateChain,
|
||||
privateKey: certPrivateKey,
|
||||
serialNumber,
|
||||
cert,
|
||||
ca: expandInternalCa(ca)
|
||||
cert
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,19 +25,19 @@ export const pkiAlertDALFactory = (db: TDbClient) => {
|
||||
recipientEmails: string;
|
||||
};
|
||||
|
||||
// SHEEN TODO: FIX REGRESION HERE
|
||||
// gets CAs and certificates as part of PKI collection items
|
||||
const combinedQuery = db
|
||||
.replicaNode()
|
||||
.select(
|
||||
db.raw("? as type", [PkiItemType.CA]),
|
||||
`${PkiItemType.CA}.id`,
|
||||
`${PkiItemType.CA}.notAfter as expiryDate`,
|
||||
`${PkiItemType.CA}.serialNumber`,
|
||||
`${PkiItemType.CA}.friendlyName`,
|
||||
"ic.notAfter as expiryDate",
|
||||
"ic.serialNumber",
|
||||
"ic.friendlyName",
|
||||
"pci.pkiCollectionId"
|
||||
)
|
||||
.from(`${TableName.CertificateAuthority} as ${PkiItemType.CA}`)
|
||||
.join(`${TableName.InternalCertificateAuthority} as ic`, `${PkiItemType.CA}.id`, "ic.certificateAuthorityId")
|
||||
.join(`${TableName.PkiCollectionItem} as pci`, `${PkiItemType.CA}.id`, "pci.caId")
|
||||
.unionAll((qb) => {
|
||||
void qb
|
||||
|
||||
@@ -320,6 +320,10 @@ export const pkiSubscriberServiceFactory = ({
|
||||
throw new BadRequestError({ message: "CA does not support ordering of certificates" });
|
||||
}
|
||||
|
||||
if (ca.externalCa?.status !== CaStatus.ACTIVE) {
|
||||
throw new BadRequestError({ message: "CA is disabled" });
|
||||
}
|
||||
|
||||
if (ca.externalCa?.id && ca.externalCa.type === CaType.ACME) {
|
||||
await certificateAuthorityQueue.orderCertificateForSubscriber({
|
||||
subscriberId: subscriber.id,
|
||||
|
||||
Reference in New Issue
Block a user