mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +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,
|
||||
SecretSyncLock: (syncId: string) => `secret-sync-mutex-${syncId}` 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,
|
||||
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
||||
`identity-access-token-status:${identityAccessTokenId}`,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||
import {
|
||||
TFailedIntegrationSyncEmailsPayload,
|
||||
TIntegrationSyncPayload,
|
||||
@@ -44,6 +45,7 @@ export enum QueueName {
|
||||
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
||||
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||
CaCrlRotation = "ca-crl-rotation",
|
||||
CaLifecycle = "ca-lifecycle", // parent queue to ca-order-certificate-for-subscriber
|
||||
SecretReplication = "secret-replication",
|
||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||
ProjectV3Migration = "project-v3-migration",
|
||||
@@ -84,7 +86,8 @@ export enum QueueJobs {
|
||||
SecretRotationV2QueueRotations = "secret-rotation-v2-queue-rotations",
|
||||
SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets",
|
||||
SecretRotationV2SendNotification = "secret-rotation-v2-send-notification",
|
||||
InvalidateCache = "invalidate-cache"
|
||||
InvalidateCache = "invalidate-cache",
|
||||
CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber"
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
@@ -850,16 +850,6 @@ export const registerRoutes = async (
|
||||
permissionService
|
||||
});
|
||||
|
||||
const certificateAuthorityQueue = certificateAuthorityQueueFactory({
|
||||
certificateAuthorityCrlDAL,
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthoritySecretDAL,
|
||||
certificateDAL,
|
||||
projectDAL,
|
||||
kmsService,
|
||||
queueService
|
||||
});
|
||||
|
||||
const sshCertificateAuthorityService = sshCertificateAuthorityServiceFactory({
|
||||
sshCertificateAuthorityDAL,
|
||||
sshCertificateAuthoritySecretDAL,
|
||||
@@ -906,24 +896,6 @@ export const registerRoutes = async (
|
||||
groupDAL
|
||||
});
|
||||
|
||||
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCertDAL,
|
||||
certificateAuthoritySecretDAL,
|
||||
certificateAuthorityCrlDAL,
|
||||
certificateTemplateDAL,
|
||||
certificateAuthorityQueue,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
pkiCollectionDAL,
|
||||
pkiCollectionItemDAL,
|
||||
projectDAL,
|
||||
internalCertificateAuthorityDAL,
|
||||
kmsService,
|
||||
permissionService
|
||||
});
|
||||
|
||||
const certificateAuthorityCrlService = certificateAuthorityCrlServiceFactory({
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCrlDAL,
|
||||
@@ -943,17 +915,6 @@ export const registerRoutes = async (
|
||||
licenseService
|
||||
});
|
||||
|
||||
const certificateEstService = certificateEstServiceFactory({
|
||||
internalCertificateAuthorityService,
|
||||
certificateTemplateService,
|
||||
certificateTemplateDAL,
|
||||
certificateAuthorityCertDAL,
|
||||
certificateAuthorityDAL,
|
||||
projectDAL,
|
||||
kmsService,
|
||||
licenseService
|
||||
});
|
||||
|
||||
const pkiAlertService = pkiAlertServiceFactory({
|
||||
pkiAlertDAL,
|
||||
pkiCollectionDAL,
|
||||
@@ -1627,6 +1588,52 @@ export const registerRoutes = async (
|
||||
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({
|
||||
kmipClientDAL,
|
||||
permissionService,
|
||||
@@ -1673,7 +1680,12 @@ export const registerRoutes = async (
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
externalCertificateAuthorityDAL,
|
||||
internalCertificateAuthorityService
|
||||
internalCertificateAuthorityService,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
pkiSubscriberDAL
|
||||
});
|
||||
|
||||
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
||||
@@ -1688,9 +1700,7 @@ export const registerRoutes = async (
|
||||
projectDAL,
|
||||
kmsService,
|
||||
permissionService,
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
externalCertificateAuthorityDAL
|
||||
certificateAuthorityQueue
|
||||
});
|
||||
|
||||
await secretRotationV2QueueServiceFactory({
|
||||
|
||||
@@ -288,7 +288,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiSubscribers],
|
||||
description: "Issue certificate",
|
||||
description: "Order certificate",
|
||||
params: z.object({
|
||||
subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName)
|
||||
}),
|
||||
@@ -297,16 +297,12 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificate),
|
||||
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)
|
||||
message: z.string().trim()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
await server.services.pkiSubscriber.issueSubscriberCert({
|
||||
const subscriber = await server.services.pkiSubscriber.orderSubscriberCert({
|
||||
subscriberName: req.params.subscriberName,
|
||||
projectId: req.body.projectId,
|
||||
actor: req.permission.type,
|
||||
@@ -315,35 +311,31 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: subscriber.projectId,
|
||||
event: {
|
||||
type: EventType.ISSUE_PKI_SUBSCRIBER_CERT,
|
||||
metadata: {
|
||||
subscriberId: subscriber.id,
|
||||
name: subscriber.name,
|
||||
serialNumber
|
||||
}
|
||||
}
|
||||
});
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// projectId: subscriber.projectId,
|
||||
// event: {
|
||||
// type: EventType.ISSUE_PKI_SUBSCRIBER_CERT,
|
||||
// metadata: {
|
||||
// subscriberId: subscriber.id,
|
||||
// name: subscriber.name,
|
||||
// serialNumber
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.IssueCert,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
properties: {
|
||||
subscriberId: subscriber.id,
|
||||
commonName: subscriber.commonName,
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
// await server.services.telemetry.sendPostHogEvents({
|
||||
// event: PostHogEventTypes.IssueCert,
|
||||
// distinctId: getTelemetryDistinctId(req),
|
||||
// properties: {
|
||||
// subscriberId: subscriber.id,
|
||||
// commonName: subscriber.commonName,
|
||||
// ...req.auditLogInfo
|
||||
// }
|
||||
// });
|
||||
|
||||
return {
|
||||
certificate,
|
||||
certificateChain,
|
||||
issuingCaCertificate,
|
||||
privateKey,
|
||||
serialNumber
|
||||
message: "Successfully placed order for certificate"
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,11 +3,12 @@ import * as x509 from "@peculiar/x509";
|
||||
import acme from "acme-client";
|
||||
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 { OrgServiceActor } from "@app/lib/types";
|
||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||
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 { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
||||
import { TAwsConnection, TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types";
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
CertStatus
|
||||
} from "@app/services/certificate/certificate-types";
|
||||
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 { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
@@ -30,6 +32,7 @@ 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";
|
||||
import {
|
||||
TAcmeCertificateAuthority,
|
||||
TCreateAcmeCertificateAuthorityDTO,
|
||||
@@ -47,7 +50,11 @@ type TAcmeCertificateAuthorityFnsDeps = {
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "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">;
|
||||
};
|
||||
|
||||
@@ -59,7 +66,7 @@ type DBConfigurationColumn = {
|
||||
|
||||
export const castDbEntryToAcmeCertificateAuthority = (
|
||||
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>
|
||||
): TAcmeCertificateAuthority => {
|
||||
): TAcmeCertificateAuthority & { credentials: unknown } => {
|
||||
if (!ca.externalCa) {
|
||||
throw new BadRequestError({ message: "Malformed ACME certificate authority" });
|
||||
}
|
||||
@@ -72,6 +79,7 @@ export const castDbEntryToAcmeCertificateAuthority = (
|
||||
disableDirectIssuance: ca.disableDirectIssuance,
|
||||
name: ca.externalCa.name,
|
||||
projectId: ca.projectId,
|
||||
credentials: ca.externalCa.credentials,
|
||||
configuration: {
|
||||
dnsAppConnectionId: ca.externalCa.dnsAppConnectionId as string,
|
||||
dnsProvider: dbConfigurationCol.dnsProvider as AcmeDnsProvider,
|
||||
@@ -147,7 +155,8 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
pkiSubscriberDAL
|
||||
}: TAcmeCertificateAuthorityFnsDeps) => {
|
||||
const createCertificateAuthority = async ({
|
||||
name,
|
||||
@@ -320,20 +329,66 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
return cas.map(castDbEntryToAcmeCertificateAuthority);
|
||||
};
|
||||
|
||||
// SHEEN TODO: need to execute this from a job
|
||||
const orderCertificate = async (
|
||||
subscriber: TPkiSubscribers,
|
||||
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const orderCertificate = async (subscriberId: string) => {
|
||||
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||
if (!subscriber.caId) {
|
||||
throw new BadRequestError({ message: "Subscriber does not have a CA" });
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// SHEEN TODO: need to save this in credentials field and reuse
|
||||
const privateRsaKey = await acme.crypto.createPrivateRsaKey();
|
||||
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||
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({
|
||||
directoryUrl: acmeCa.configuration.directoryUrl,
|
||||
accountKey: privateRsaKey
|
||||
accountKey
|
||||
});
|
||||
|
||||
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||
@@ -349,13 +404,8 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
skLeaf
|
||||
);
|
||||
|
||||
// SHEEN TODO: need to update this to remove dependence on ACTOR
|
||||
const appConnection = await appConnectionDAL.findById(acmeCa.configuration.dnsAppConnectionId);
|
||||
const connection = await appConnectionService.connectAppConnectionById(
|
||||
appConnection.app as AppConnection,
|
||||
acmeCa.configuration.dnsAppConnectionId,
|
||||
actor
|
||||
);
|
||||
const connection = await decryptAppConnection(appConnection, kmsService);
|
||||
|
||||
const pem = await acmeClient.auto({
|
||||
csr: certificateCsr,
|
||||
@@ -385,20 +435,9 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
}
|
||||
});
|
||||
|
||||
console.log("PEM IS", pem);
|
||||
|
||||
const [leafCert, parentCert] = acme.crypto.splitPemChain(pem);
|
||||
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({
|
||||
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
||||
});
|
||||
|
||||
@@ -15,6 +15,10 @@ export const AcmeCertificateAuthorityConfigurationSchema = z.object({
|
||||
accountEmail: z.string().trim()
|
||||
});
|
||||
|
||||
export const AcmeCertificateAuthorityCredentialsSchema = z.object({
|
||||
accountKey: z.string()
|
||||
});
|
||||
|
||||
export const AcmeCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.ACME).extend({
|
||||
type: z.literal(CaType.ACME),
|
||||
configuration: AcmeCertificateAuthorityConfigurationSchema
|
||||
|
||||
@@ -58,6 +58,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
||||
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
|
||||
db.ref("status").withSchema(TableName.ExternalCertificateAuthority).as("externalStatus"),
|
||||
db.ref("configuration").withSchema(TableName.ExternalCertificateAuthority).as("externalConfiguration"),
|
||||
db.ref("credentials").withSchema(TableName.ExternalCertificateAuthority).as("externalCredentials"),
|
||||
db
|
||||
.ref("dnsAppConnectionId")
|
||||
.withSchema(TableName.ExternalCertificateAuthority)
|
||||
@@ -99,7 +100,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
||||
status: result.externalStatus,
|
||||
configuration: result.externalConfiguration,
|
||||
dnsAppConnectionId: result.externalDnsAppConnectionId,
|
||||
appConnectionId: result.externalAppConnectionId
|
||||
appConnectionId: result.externalAppConnectionId,
|
||||
credentials: result.externalCredentials
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
@@ -195,6 +197,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
||||
.ref("dnsAppConnectionId")
|
||||
.withSchema(TableName.ExternalCertificateAuthority)
|
||||
.as("externalDnsAppConnectionId"),
|
||||
db.ref("credentials").withSchema(TableName.ExternalCertificateAuthority).as("externalCredentials"),
|
||||
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
||||
);
|
||||
|
||||
@@ -243,7 +246,8 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
||||
status: ca.externalStatus,
|
||||
configuration: ca.externalConfiguration,
|
||||
dnsAppConnectionId: ca.externalDnsAppConnectionId,
|
||||
appConnectionId: ca.externalAppConnectionId
|
||||
appConnectionId: ca.externalAppConnectionId,
|
||||
credentials: ca.externalCredentials
|
||||
}
|
||||
: undefined
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as x509 from "@peculiar/x509";
|
||||
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";
|
||||
@@ -13,21 +14,42 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
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 { CaType } from "./certificate-authority-enums";
|
||||
import { keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
|
||||
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 = {
|
||||
// TODO: Pick
|
||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
|
||||
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
|
||||
certificateDAL: TCertificateDALFactory;
|
||||
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;
|
||||
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "findById">;
|
||||
};
|
||||
|
||||
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
|
||||
|
||||
export const certificateAuthorityQueueFactory = ({
|
||||
@@ -37,8 +59,28 @@ export const certificateAuthorityQueueFactory = ({
|
||||
certificateDAL,
|
||||
projectDAL,
|
||||
kmsService,
|
||||
queueService
|
||||
queueService,
|
||||
keyStore,
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
pkiSubscriberDAL
|
||||
}: TCertificateAuthorityQueueFactoryDep) => {
|
||||
const acmeFns = AcmeCertificateAuthorityFns({
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
certificateAuthorityDAL,
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
pkiSubscriberDAL,
|
||||
projectDAL
|
||||
});
|
||||
|
||||
// TODO 1: auto-periodic 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) => {
|
||||
const { caId } = job.data;
|
||||
logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
|
||||
@@ -144,6 +231,7 @@ export const certificateAuthorityQueueFactory = ({
|
||||
});
|
||||
|
||||
return {
|
||||
setCaCrlRotationInterval
|
||||
setCaCrlRotationInterval,
|
||||
orderCertificateForSubscriber
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,11 @@ import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||
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 { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
|
||||
import {
|
||||
@@ -46,6 +51,14 @@ type TCertificateAuthorityServiceFactoryDep = {
|
||||
"findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId"
|
||||
>;
|
||||
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>;
|
||||
@@ -57,13 +70,24 @@ export const certificateAuthorityServiceFactory = ({
|
||||
internalCertificateAuthorityService,
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
externalCertificateAuthorityDAL
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
pkiSubscriberDAL
|
||||
}: TCertificateAuthorityServiceFactoryDep) => {
|
||||
const acmeFns = AcmeCertificateAuthorityFns({
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
certificateAuthorityDAL,
|
||||
externalCertificateAuthorityDAL
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
pkiSubscriberDAL,
|
||||
projectDAL
|
||||
});
|
||||
|
||||
const createCertificateAuthority = async (
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority-cert-dal";
|
||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal";
|
||||
import { CaRenewalType, CaStatus, InternalCaType } from "../certificate-authority-enums";
|
||||
import { CaRenewalType, CaStatus, CaType, InternalCaType } from "../certificate-authority-enums";
|
||||
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
||||
import {
|
||||
CreateInternalCertificateAuthoritySchema,
|
||||
@@ -212,3 +212,8 @@ export type TRotateCaCrlTriggerDTO = {
|
||||
caId: string;
|
||||
rotationIntervalDays: number;
|
||||
};
|
||||
|
||||
export type TOrderCertificateForSubscriberDTO = {
|
||||
subscriberId: string;
|
||||
caType: CaType;
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ 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()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { PkiSubscribersSchema } from "@app/db/schemas";
|
||||
|
||||
export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
||||
@@ -11,4 +13,6 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
||||
ttl: true,
|
||||
keyUsages: 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 { 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 { AcmeCertificateAuthorityFns } from "../certificate-authority/acme/acme-certificate-authority-fns";
|
||||
import { TExternalCertificateAuthorityDALFactory } from "../certificate-authority/external-certificate-authority-dal";
|
||||
import { TCertificateAuthorityQueueFactory } from "../certificate-authority/certificate-authority-queue";
|
||||
import { InternalCertificateAuthorityFns } from "../certificate-authority/internal/internal-certificate-authority-fns";
|
||||
import {
|
||||
PkiSubscriberStatus,
|
||||
@@ -57,9 +54,6 @@ import {
|
||||
} from "./pki-subscriber-types";
|
||||
|
||||
type TPkiSubscriberServiceFactoryDep = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||
pkiSubscriberDAL: Pick<
|
||||
TPkiSubscriberDALFactory,
|
||||
"create" | "findById" | "updateById" | "deleteById" | "transaction" | "find" | "findOne"
|
||||
@@ -70,6 +64,7 @@ type TPkiSubscriberServiceFactoryDep = {
|
||||
>;
|
||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
||||
certificateAuthorityQueue: Pick<TCertificateAuthorityQueueFactory, "orderCertificateForSubscriber">;
|
||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction" | "countCertificatesForPkiSubscriber" | "find">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||
@@ -93,9 +88,7 @@ export const pkiSubscriberServiceFactory = ({
|
||||
projectDAL,
|
||||
kmsService,
|
||||
permissionService,
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
externalCertificateAuthorityDAL
|
||||
certificateAuthorityQueue
|
||||
}: TPkiSubscriberServiceFactoryDep) => {
|
||||
const internalCaFns = InternalCertificateAuthorityFns({
|
||||
certificateAuthorityDAL,
|
||||
@@ -109,18 +102,6 @@ export const pkiSubscriberServiceFactory = ({
|
||||
kmsService
|
||||
});
|
||||
|
||||
const acmeCaFns = AcmeCertificateAuthorityFns({
|
||||
appConnectionDAL,
|
||||
appConnectionService,
|
||||
certificateAuthorityDAL,
|
||||
externalCertificateAuthorityDAL,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL
|
||||
});
|
||||
|
||||
const createSubscriber = async ({
|
||||
name,
|
||||
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 ({
|
||||
@@ -325,16 +317,16 @@ export const pkiSubscriberServiceFactory = ({
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId);
|
||||
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) {
|
||||
return acmeCaFns.orderCertificate(subscriber, ca, {
|
||||
type: actor,
|
||||
id: actorId,
|
||||
authMethod: actorAuthMethod,
|
||||
orgId: actorOrgId
|
||||
await certificateAuthorityQueue.orderCertificateForSubscriber({
|
||||
subscriberId: subscriber.id,
|
||||
caType: ca.externalCa.type
|
||||
});
|
||||
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Unsupported CA type" });
|
||||
@@ -380,15 +372,6 @@ export const pkiSubscriberServiceFactory = ({
|
||||
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" });
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export {
|
||||
useCreatePkiSubscriber,
|
||||
useDeletePkiSubscriber,
|
||||
useIssuePkiSubscriberCert,
|
||||
useOrderPkiSubscriberCert,
|
||||
useUpdatePkiSubscriber
|
||||
} from "./mutations";
|
||||
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[];
|
||||
keyUsages: CertKeyUsage[];
|
||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||
supportsImmediateCertIssuance?: boolean;
|
||||
};
|
||||
|
||||
export type TCreatePkiSubscriberDTO = {
|
||||
@@ -51,3 +52,8 @@ export type TIssuePkiSubscriberCertDTO = {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TOrderPkiSubscriberCertDTO = {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
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 { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -49,23 +53,34 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
|
||||
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
|
||||
useIssuePkiSubscriberCert();
|
||||
|
||||
const { mutateAsync: orderPkiSubscriberCert } = useOrderPkiSubscriberCert();
|
||||
|
||||
const onIssuePkiSubscriberCert = async () => {
|
||||
try {
|
||||
const response = await issuePkiSubscriberCert({ subscriberName, projectId });
|
||||
if (pkiSubscriber?.supportsImmediateCertIssuance) {
|
||||
const response = await issuePkiSubscriberCert({ subscriberName, projectId });
|
||||
|
||||
setCertificateDetails({
|
||||
serialNumber: response.serialNumber,
|
||||
certificate: response.certificate,
|
||||
certificateChain: response.certificateChain,
|
||||
privateKey: response.privateKey
|
||||
});
|
||||
setCertificateDetails({
|
||||
serialNumber: response.serialNumber,
|
||||
certificate: response.certificate,
|
||||
certificateChain: response.certificateChain,
|
||||
privateKey: response.privateKey
|
||||
});
|
||||
|
||||
setIsModalOpen(true);
|
||||
setIsModalOpen(true);
|
||||
|
||||
createNotification({
|
||||
text: "Successfully issued certificate",
|
||||
type: "success"
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully issued certificate",
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
await orderPkiSubscriberCert({ subscriberName, projectId });
|
||||
|
||||
createNotification({
|
||||
text: "Successfully ordered certificate. It will be issued after CA processing.",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
@@ -158,7 +173,9 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
|
||||
onIssuePkiSubscriberCert();
|
||||
}}
|
||||
>
|
||||
Issue Certificate
|
||||
{pkiSubscriber?.supportsImmediateCertIssuance
|
||||
? "Issue Certificate"
|
||||
: "Order Certificate"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user