mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: addressed comments
This commit is contained in:
@@ -225,6 +225,7 @@ export enum EventType {
|
||||
REMOVE_HOST_FROM_SSH_HOST_GROUP = "remove-host-from-ssh-host-group",
|
||||
CREATE_CA = "create-certificate-authority",
|
||||
GET_CA = "get-certificate-authority",
|
||||
GET_CAS = "get-certificate-authorities",
|
||||
UPDATE_CA = "update-certificate-authority",
|
||||
DELETE_CA = "delete-certificate-authority",
|
||||
RENEW_CA = "renew-certificate-authority",
|
||||
@@ -1718,7 +1719,7 @@ interface CreateCa {
|
||||
type: EventType.CREATE_CA;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
dn?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1726,7 +1727,14 @@ interface GetCa {
|
||||
type: EventType.GET_CA;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
dn?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCAs {
|
||||
type: EventType.GET_CAS;
|
||||
metadata: {
|
||||
caIds: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1734,7 +1742,7 @@ interface UpdateCa {
|
||||
type: EventType.UPDATE_CA;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
dn?: string;
|
||||
status: CaStatus;
|
||||
};
|
||||
}
|
||||
@@ -1743,7 +1751,7 @@ interface DeleteCa {
|
||||
type: EventType.DELETE_CA;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
dn?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2031,7 +2039,7 @@ interface IssuePkiSubscriberCert {
|
||||
metadata: {
|
||||
subscriberId: string;
|
||||
name: string;
|
||||
serialNumber: string;
|
||||
serialNumber?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2987,6 +2995,7 @@ export type Event =
|
||||
| IssueSshHostHostCert
|
||||
| CreateCa
|
||||
| GetCa
|
||||
| GetCAs
|
||||
| UpdateCa
|
||||
| DeleteCa
|
||||
| RenewCa
|
||||
|
||||
@@ -39,7 +39,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
if (!caCrl) throw new NotFoundError({ message: `CRL with ID '${crlId}' not found` });
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caCrl.caId);
|
||||
if (!ca?.internalCa) throw new NotFoundError({ message: `CA with ID '${caCrl.caId}' not found` });
|
||||
if (!ca?.internalCa?.id) throw new NotFoundError({ message: `Internal CA with ID '${caCrl.caId}' not found` });
|
||||
|
||||
const keyId = await getProjectKmsCertificateKeyId({
|
||||
projectId: ca.projectId,
|
||||
@@ -67,7 +67,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
*/
|
||||
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
|
||||
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,
|
||||
|
||||
@@ -228,9 +228,9 @@ export const certificateEstServiceFactory = ({
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(certTemplate.caId);
|
||||
if (!ca?.internalCa) {
|
||||
if (!ca?.internalCa?.id) {
|
||||
throw new NotFoundError({
|
||||
message: `Certificate Authority with ID '${certTemplate.caId}' not found`
|
||||
message: `Internal Certificate Authority with ID '${certTemplate.caId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ApiDocsTags } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||
import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||
import {
|
||||
TCertificateAuthority,
|
||||
TCertificateAuthorityInput
|
||||
@@ -26,11 +26,13 @@ export const registerCertificateAuthorityEndpoints = <
|
||||
createSchema: z.ZodType<{
|
||||
name: string;
|
||||
projectId: string;
|
||||
status: CaStatus;
|
||||
configuration: I["configuration"];
|
||||
disableDirectIssuance: boolean;
|
||||
}>;
|
||||
updateSchema: z.ZodType<{
|
||||
name?: string;
|
||||
status?: CaStatus;
|
||||
configuration?: I["configuration"];
|
||||
disableDirectIssuance?: boolean;
|
||||
}>;
|
||||
@@ -63,18 +65,16 @@ export const registerCertificateAuthorityEndpoints = <
|
||||
req.permission
|
||||
)) as T[];
|
||||
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// projectId,
|
||||
// event: {
|
||||
// type: EventType.GET_SECRET_SYNCS,
|
||||
// metadata: {
|
||||
// destination,
|
||||
// count: secretSyncs.length,
|
||||
// syncIds: secretSyncs.map((connection) => connection.id)
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_CAS,
|
||||
metadata: {
|
||||
caIds: certificateAuthorities.map((ca) => ca.id)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { certificateAuthorities };
|
||||
}
|
||||
@@ -105,17 +105,16 @@ export const registerCertificateAuthorityEndpoints = <
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// projectId: secretSync.projectId,
|
||||
// event: {
|
||||
// type: EventType.GET_SECRET_SYNC,
|
||||
// metadata: {
|
||||
// syncId,
|
||||
// destination
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: certificateAuthority.projectId,
|
||||
event: {
|
||||
type: EventType.GET_CA,
|
||||
metadata: {
|
||||
caId: certificateAuthority.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { certificateAuthority };
|
||||
}
|
||||
@@ -142,18 +141,16 @@ export const registerCertificateAuthorityEndpoints = <
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// projectId: secretSync.projectId,
|
||||
// event: {
|
||||
// type: EventType.CREATE_SECRET_SYNC,
|
||||
// metadata: {
|
||||
// syncId: secretSync.id,
|
||||
// destination,
|
||||
// ...req.body
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: certificateAuthority.projectId,
|
||||
event: {
|
||||
type: EventType.CREATE_CA,
|
||||
metadata: {
|
||||
caId: certificateAuthority.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { certificateAuthority };
|
||||
}
|
||||
@@ -181,22 +178,25 @@ export const registerCertificateAuthorityEndpoints = <
|
||||
const { certificateAuthorityId } = req.params;
|
||||
|
||||
const certificateAuthority = (await server.services.certificateAuthority.updateCertificateAuthority(
|
||||
{ ...req.body, id: certificateAuthorityId, type: caType },
|
||||
{
|
||||
...req.body,
|
||||
id: certificateAuthorityId,
|
||||
type: caType
|
||||
},
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// projectId: certificateAuthority.projectId,
|
||||
// event: {
|
||||
// type: EventType.UPDATE_SECRET_SYNC,
|
||||
// metadata: {
|
||||
// syncId,
|
||||
// destination,
|
||||
// ...req.body
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: certificateAuthority.projectId,
|
||||
event: {
|
||||
type: EventType.UPDATE_CA,
|
||||
metadata: {
|
||||
caId: certificateAuthority.id,
|
||||
status: certificateAuthority.status
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { certificateAuthority };
|
||||
}
|
||||
@@ -227,18 +227,16 @@ export const registerCertificateAuthorityEndpoints = <
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// orgId: req.permission.orgId,
|
||||
// event: {
|
||||
// type: EventType.DELETE_SECRET_SYNC,
|
||||
// metadata: {
|
||||
// destination,
|
||||
// syncId,
|
||||
// removeSecrets
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: certificateAuthority.projectId,
|
||||
event: {
|
||||
type: EventType.DELETE_CA,
|
||||
metadata: {
|
||||
caId: certificateAuthority.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { certificateAuthority };
|
||||
}
|
||||
|
||||
@@ -311,28 +311,27 @@ 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
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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 {
|
||||
message: "Successfully placed order for certificate"
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,10 +4,12 @@ export {
|
||||
useCreateCertificate,
|
||||
useCreateUnifiedCa,
|
||||
useDeleteCa,
|
||||
useDeleteUnifiedCa,
|
||||
useImportCaCertificate,
|
||||
useRenewCa,
|
||||
useSignIntermediate,
|
||||
useUpdateCa
|
||||
useUpdateCa,
|
||||
useUpdateUnifiedCa
|
||||
} from "./mutations";
|
||||
export {
|
||||
useGetCaById,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TCreateCertificateResponse,
|
||||
TCreateUnifiedCertificateAuthorityDTO,
|
||||
TDeleteCaDTO,
|
||||
TDeleteUnifiedCertificateAuthorityDTO,
|
||||
TImportCaCertificateDTO,
|
||||
TImportCaCertificateResponse,
|
||||
TRenewCaDTO,
|
||||
@@ -18,9 +19,31 @@ import {
|
||||
TSignIntermediateDTO,
|
||||
TSignIntermediateResponse,
|
||||
TUnifiedCertificateAuthority,
|
||||
TUpdateCaDTO
|
||||
TUpdateCaDTO,
|
||||
TUpdateUnifiedCertificateAuthorityDTO
|
||||
} from "./types";
|
||||
|
||||
export const useUpdateUnifiedCa = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TUnifiedCertificateAuthority, object, TUpdateUnifiedCertificateAuthorityDTO>({
|
||||
mutationFn: async ({ id, ...body }) => {
|
||||
const {
|
||||
data: { certificateAuthority }
|
||||
} = await apiRequest.patch<{ certificateAuthority: TUnifiedCertificateAuthority }>(
|
||||
`/api/v1/pki/ca/${body.type}/${id}`,
|
||||
body
|
||||
);
|
||||
|
||||
return certificateAuthority;
|
||||
},
|
||||
onSuccess: ({ projectId, type }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: caKeys.listCasByTypeAndProjectId(type, projectId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateUnifiedCa = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TUnifiedCertificateAuthority, object, TCreateUnifiedCertificateAuthorityDTO>({
|
||||
@@ -39,6 +62,25 @@ export const useCreateUnifiedCa = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteUnifiedCa = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TUnifiedCertificateAuthority, object, TDeleteUnifiedCertificateAuthorityDTO>({
|
||||
mutationFn: async ({ caId, type }) => {
|
||||
const {
|
||||
data: { certificateAuthority }
|
||||
} = await apiRequest.delete<{ certificateAuthority: TUnifiedCertificateAuthority }>(
|
||||
`/api/v1/pki/ca/${type}/${caId}`
|
||||
);
|
||||
return certificateAuthority;
|
||||
},
|
||||
onSuccess: (_, { type, projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: caKeys.listCasByTypeAndProjectId(type, projectId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateCa = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TCertificateAuthority, object, TCreateCaDTO>({
|
||||
|
||||
@@ -10,7 +10,10 @@ export type TAcmeCertificateAuthority = {
|
||||
disableDirectIssuance: boolean;
|
||||
configuration: {
|
||||
dnsAppConnectionId: string;
|
||||
dnsProvider: AcmeDnsProvider;
|
||||
dnsProviderConfig: {
|
||||
provider: AcmeDnsProvider.ROUTE53;
|
||||
hostedZoneId: string;
|
||||
};
|
||||
directoryUrl: string;
|
||||
accountEmail: string;
|
||||
};
|
||||
@@ -48,6 +51,16 @@ export type TUnifiedCertificateAuthority =
|
||||
| TInternalCertificateAuthority;
|
||||
|
||||
export type TCreateUnifiedCertificateAuthorityDTO = Omit<TUnifiedCertificateAuthority, "id">;
|
||||
export type TUpdateUnifiedCertificateAuthorityDTO = Partial<TUnifiedCertificateAuthority> & {
|
||||
id: string;
|
||||
type: CaType;
|
||||
};
|
||||
|
||||
export type TDeleteUnifiedCertificateAuthorityDTO = {
|
||||
caId: string;
|
||||
type: CaType;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TCertificateAuthority = {
|
||||
id: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { TReactQueryOptions } from "@app/types/reactQuery";
|
||||
|
||||
import { TCertificate } from "../certificates/types";
|
||||
import { TPkiSubscriber } from "./types";
|
||||
@@ -62,17 +63,20 @@ export const useGetPkiSubscriber = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPkiSubscriberCertificates = ({
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset,
|
||||
limit
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}) => {
|
||||
export const useGetPkiSubscriberCertificates = (
|
||||
{
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset,
|
||||
limit
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
},
|
||||
options?: TReactQueryOptions["options"]
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: pkiSubscriberKeys.specificPkiSubscriberCertificates({
|
||||
subscriberName,
|
||||
@@ -97,6 +101,7 @@ export const useGetPkiSubscriberCertificates = ({
|
||||
);
|
||||
return { certificates, totalCount };
|
||||
},
|
||||
enabled: Boolean(subscriberName) && Boolean(projectId)
|
||||
enabled: Boolean(subscriberName) && Boolean(projectId),
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
@@ -23,23 +23,27 @@ import {
|
||||
CaStatus,
|
||||
CaType,
|
||||
useCreateUnifiedCa,
|
||||
useGetCaById,
|
||||
useUpdateCa
|
||||
useGetCaByTypeAndId,
|
||||
useUpdateUnifiedCa
|
||||
} from "@app/hooks/api/ca";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
type: z.enum([CaType.ACME]),
|
||||
type: z.nativeEnum(CaType),
|
||||
name: z.string(),
|
||||
disableDirectIssuance: z.boolean(),
|
||||
status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]),
|
||||
status: z.nativeEnum(CaStatus),
|
||||
configuration: z.object({
|
||||
dnsAppConnection: z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
}),
|
||||
dnsProvider: z.nativeEnum(AcmeDnsProvider),
|
||||
// currently specific to Route53 but can be extended to others by differentiating via the provider property
|
||||
dnsProviderConfig: z.object({
|
||||
provider: z.nativeEnum(AcmeDnsProvider),
|
||||
hostedZoneId: z.string()
|
||||
}),
|
||||
directoryUrl: z.string(),
|
||||
accountEmail: z.string()
|
||||
})
|
||||
@@ -58,11 +62,13 @@ const caTypes = [{ label: "ACME", value: CaType.ACME }];
|
||||
export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: ca } = useGetCaById((popUp?.ca?.data as { caId: string })?.caId || "");
|
||||
const { data: ca } = useGetCaByTypeAndId(
|
||||
(popUp?.ca?.data as { type: CaType })?.type || "",
|
||||
(popUp?.ca?.data as { caId: string })?.caId || ""
|
||||
);
|
||||
|
||||
// SHEEN TODO: finish up CA management
|
||||
const { mutateAsync: createMutateAsync } = useCreateUnifiedCa();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateCa();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateUnifiedCa();
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -82,7 +88,10 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
id: "",
|
||||
name: ""
|
||||
},
|
||||
dnsProvider: AcmeDnsProvider.ROUTE53,
|
||||
dnsProviderConfig: {
|
||||
provider: AcmeDnsProvider.ROUTE53,
|
||||
hostedZoneId: ""
|
||||
},
|
||||
directoryUrl: "",
|
||||
accountEmail: ""
|
||||
}
|
||||
@@ -90,7 +99,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
});
|
||||
|
||||
const caType = watch("type");
|
||||
const dnsProvider = watch("configuration.dnsProvider");
|
||||
const dnsProvider = watch("configuration.dnsProviderConfig.provider");
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
AppConnection.AWS,
|
||||
@@ -101,11 +110,30 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (ca) {
|
||||
// reset({
|
||||
// type: ca.type,
|
||||
// name: ca.name,
|
||||
// disableDirectIssuance: ca.disableDirectIssuance
|
||||
// });
|
||||
if (ca.type !== CaType.INTERNAL && availableConnections?.length) {
|
||||
const selectedConnection = availableConnections?.find(
|
||||
(connection) => connection.id === ca?.configuration.dnsAppConnectionId
|
||||
);
|
||||
|
||||
reset({
|
||||
type: ca.type,
|
||||
name: ca.name,
|
||||
status: ca.status,
|
||||
disableDirectIssuance: ca.disableDirectIssuance,
|
||||
configuration: {
|
||||
dnsAppConnection: {
|
||||
id: ca.configuration.dnsAppConnectionId,
|
||||
name: selectedConnection?.name || ""
|
||||
},
|
||||
dnsProviderConfig: {
|
||||
provider: ca.configuration.dnsProviderConfig.provider,
|
||||
hostedZoneId: ca.configuration.dnsProviderConfig.hostedZoneId
|
||||
},
|
||||
directoryUrl: ca.configuration.directoryUrl,
|
||||
accountEmail: ca.configuration.accountEmail
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
reset({
|
||||
type: CaType.ACME,
|
||||
@@ -117,13 +145,16 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
id: "",
|
||||
name: ""
|
||||
},
|
||||
dnsProvider: AcmeDnsProvider.ROUTE53,
|
||||
dnsProviderConfig: {
|
||||
provider: AcmeDnsProvider.ROUTE53,
|
||||
hostedZoneId: ""
|
||||
},
|
||||
directoryUrl: "",
|
||||
accountEmail: ""
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [ca]);
|
||||
}, [ca, availableConnections]);
|
||||
|
||||
const onFormSubmit = async ({
|
||||
type,
|
||||
@@ -135,17 +166,20 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
try {
|
||||
if (!currentWorkspace?.slug) return;
|
||||
|
||||
if (ca) {
|
||||
// update
|
||||
// await updateMutateAsync({
|
||||
// projectSlug: currentWorkspace.slug,
|
||||
// caId: ca.id,
|
||||
// name,
|
||||
// disableDirectIssuance,
|
||||
// status
|
||||
// });
|
||||
if (ca && type !== CaType.INTERNAL) {
|
||||
await updateMutateAsync({
|
||||
id: ca.id,
|
||||
projectId: currentWorkspace.id,
|
||||
name,
|
||||
type,
|
||||
status,
|
||||
disableDirectIssuance,
|
||||
configuration: {
|
||||
...configuration,
|
||||
dnsAppConnectionId: configuration.dnsAppConnection.id
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// create
|
||||
await createMutateAsync({
|
||||
projectId: currentWorkspace.id,
|
||||
name,
|
||||
@@ -217,7 +251,12 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
|
||||
<FormControl
|
||||
label="Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="my-external-ca" isDisabled={Boolean(ca)} />
|
||||
</FormControl>
|
||||
)}
|
||||
@@ -226,7 +265,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
name="configuration.dnsProvider"
|
||||
name="configuration.dnsProviderConfig.provider"
|
||||
defaultValue={AcmeDnsProvider.ROUTE53}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
@@ -275,6 +314,21 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
control={control}
|
||||
name="configuration.dnsAppConnection"
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="configuration.dnsProviderConfig.hostedZoneId"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Hosted Zone ID"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="Z040441124N1GOOMCQYX1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
@@ -284,6 +338,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
label="Directory URL"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -301,6 +356,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
label="Account Email"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="user@infisical.com" />
|
||||
</FormControl>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { CaStatus, useDeleteCa, useUpdateCa } from "@app/hooks/api";
|
||||
import { CaStatus, CaType, useDeleteUnifiedCa, useUpdateUnifiedCa } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { ExternalCaModal } from "./ExternalCaModal";
|
||||
@@ -14,8 +14,8 @@ import { ExternalCaTable } from "./ExternalCaTable";
|
||||
|
||||
export const ExternalCaSection = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { mutateAsync: deleteCa } = useDeleteCa();
|
||||
const { mutateAsync: updateCa } = useUpdateCa();
|
||||
const { mutateAsync: deleteCa } = useDeleteUnifiedCa();
|
||||
const { mutateAsync: updateCa } = useUpdateUnifiedCa();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"ca",
|
||||
@@ -24,11 +24,11 @@ export const ExternalCaSection = () => {
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const onRemoveCaSubmit = async (caId: string) => {
|
||||
const onRemoveCaSubmit = async (caId: string, type: CaType) => {
|
||||
try {
|
||||
if (!currentWorkspace?.slug) return;
|
||||
|
||||
await deleteCa({ caId, projectSlug: currentWorkspace.slug });
|
||||
await deleteCa({ caId, type, projectId: currentWorkspace.id });
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted CA",
|
||||
@@ -44,11 +44,19 @@ export const ExternalCaSection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdateCaStatus = async ({ caId, status }: { caId: string; status: CaStatus }) => {
|
||||
const onUpdateCaStatus = async ({
|
||||
caId,
|
||||
type,
|
||||
status
|
||||
}: {
|
||||
caId: string;
|
||||
type: CaType;
|
||||
status: CaStatus;
|
||||
}) => {
|
||||
try {
|
||||
if (!currentWorkspace?.slug) return;
|
||||
|
||||
await updateCa({ caId, projectSlug: currentWorkspace.slug, status });
|
||||
await updateCa({ id: caId, type, status });
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`,
|
||||
@@ -96,7 +104,12 @@ export const ExternalCaSection = () => {
|
||||
subTitle="This action will delete other CAs and certificates below it in your CA hierarchy."
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteCa", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() => onRemoveCaSubmit((popUp?.deleteCa?.data as { caId: string })?.caId)}
|
||||
onDeleteApproved={() =>
|
||||
onRemoveCaSubmit(
|
||||
(popUp?.deleteCa?.data as { caId: string })?.caId,
|
||||
(popUp?.deleteCa?.data as { type: CaType })?.type
|
||||
)
|
||||
}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.caStatus.isOpen}
|
||||
@@ -111,9 +124,12 @@ export const ExternalCaSection = () => {
|
||||
: "This action will prevent the CA from issuing new certificates."
|
||||
}
|
||||
onChange={(isOpen) => handlePopUpToggle("caStatus", isOpen)}
|
||||
buttonText="Proceed"
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onUpdateCaStatus(popUp?.caStatus?.data as { caId: string; status: CaStatus })
|
||||
onUpdateCaStatus(
|
||||
popUp?.caStatus?.data as { caId: string; type: CaType; status: CaStatus }
|
||||
)
|
||||
}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { faBan, faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faBan,
|
||||
faCertificate,
|
||||
faEllipsis,
|
||||
faPencil,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
@@ -24,7 +29,6 @@ import {
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { CaStatus, CaType, useListCasByTypeAndProjectId } from "@app/hooks/api";
|
||||
import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -32,7 +36,7 @@ type Props = {
|
||||
popUpName: keyof UsePopUpState<["ca", "deleteCa", "caStatus", "upgradePlan"]>,
|
||||
data?: {
|
||||
caId?: string;
|
||||
dn?: string;
|
||||
type?: CaType;
|
||||
status?: CaStatus;
|
||||
description?: string;
|
||||
}
|
||||
@@ -40,7 +44,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data, isPending } = useListCasByTypeAndProjectId(CaType.ACME, currentWorkspace.id);
|
||||
|
||||
@@ -66,15 +69,12 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/ca/$caId` as const,
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
caId: ca.id
|
||||
}
|
||||
})
|
||||
}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("ca", {
|
||||
caId: ca.id,
|
||||
type: ca.type
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Td>{ca.name}</Td>
|
||||
<Td>{ca.type}</Td>
|
||||
@@ -93,6 +93,29 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.CertificateAuthorities}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("ca", {
|
||||
caId: ca.id,
|
||||
type: ca.type
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faPencil} />}
|
||||
>
|
||||
Edit CA
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
{(ca.status === CaStatus.ACTIVE || ca.status === CaStatus.DISABLED) && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
@@ -108,6 +131,7 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("caStatus", {
|
||||
caId: ca.id,
|
||||
type: ca.type,
|
||||
status:
|
||||
ca.status === CaStatus.ACTIVE
|
||||
? CaStatus.DISABLED
|
||||
@@ -133,10 +157,10 @@ export const ExternalCaTable = ({ handlePopUpOpen }: Props) => {
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// handlePopUpOpen("deleteCa", {
|
||||
// caId: ca.id,
|
||||
// dn: ca.dn
|
||||
// });
|
||||
handlePopUpOpen("deleteCa", {
|
||||
caId: ca.id,
|
||||
type: ca.type
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
|
||||
@@ -48,12 +48,17 @@ export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||
|
||||
const { data, isPending } = useGetPkiSubscriberCertificates({
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset: (page - 1) * perPage,
|
||||
limit: perPage
|
||||
});
|
||||
const { data, isPending } = useGetPkiSubscriberCertificates(
|
||||
{
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset: (page - 1) * perPage,
|
||||
limit: perPage
|
||||
},
|
||||
{
|
||||
refetchInterval: 10 * 1000 // 10 seconds
|
||||
}
|
||||
);
|
||||
|
||||
const getCertStatusBadge = (status: string, notAfter: string) => {
|
||||
if (status === CertStatus.REVOKED) {
|
||||
|
||||
Reference in New Issue
Block a user