mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 21:29:20 +00:00
919 lines
29 KiB
TypeScript
919 lines
29 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
import { z } from "zod";
|
|
|
|
import { CertificateAuthoritiesSchema, CertificateTemplatesSchema } from "@app/db/schemas";
|
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|
import { ApiDocsTags, CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
|
|
import { ms } from "@app/lib/ms";
|
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|
import { AuthMode } from "@app/services/auth/auth-type";
|
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
|
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
|
import {
|
|
validateAltNamesField,
|
|
validateCaDateField
|
|
} from "@app/services/certificate-authority/certificate-authority-validators";
|
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
|
|
|
export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|
server.route({
|
|
method: "POST",
|
|
url: "/",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Create CA",
|
|
body: z
|
|
.object({
|
|
projectSlug: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.projectSlug),
|
|
type: z.nativeEnum(CaType).describe(CERTIFICATE_AUTHORITIES.CREATE.type),
|
|
friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.CREATE.friendlyName),
|
|
commonName: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.commonName),
|
|
organization: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.organization),
|
|
ou: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.ou),
|
|
country: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.country),
|
|
province: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.province),
|
|
locality: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.locality),
|
|
// format: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
|
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.CREATE.notBefore),
|
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.CREATE.notAfter),
|
|
maxPathLength: z.number().min(-1).default(-1).describe(CERTIFICATE_AUTHORITIES.CREATE.maxPathLength),
|
|
keyAlgorithm: z
|
|
.nativeEnum(CertKeyAlgorithm)
|
|
.default(CertKeyAlgorithm.RSA_2048)
|
|
.describe(CERTIFICATE_AUTHORITIES.CREATE.keyAlgorithm),
|
|
requireTemplateForIssuance: z
|
|
.boolean()
|
|
.default(false)
|
|
.describe(CERTIFICATE_AUTHORITIES.CREATE.requireTemplateForIssuance)
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
// Check that at least one of the specified fields is non-empty
|
|
return [data.commonName, data.organization, data.ou, data.country, data.province, data.locality].some(
|
|
(field) => field !== ""
|
|
);
|
|
},
|
|
{
|
|
message:
|
|
"At least one of the fields commonName, organization, ou, country, province, or locality must be non-empty",
|
|
path: []
|
|
}
|
|
),
|
|
response: {
|
|
200: z.object({
|
|
ca: CertificateAuthoritiesSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const ca = await server.services.certificateAuthority.createCa({
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.CREATE_CA,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
ca
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET.caId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
ca: CertificateAuthoritiesSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const ca = await server.services.certificateAuthority.getCaById({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.GET_CA,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
ca
|
|
};
|
|
}
|
|
});
|
|
|
|
// this endpoint will be used to serve the CA certificate when a client makes a request
|
|
// against the Authority Information Access CA Issuer URL
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId/certificates/:caCertId/der",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get DER-encoded certificate of CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caId),
|
|
caCertId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caCertId)
|
|
}),
|
|
response: {
|
|
200: z.instanceof(Buffer)
|
|
}
|
|
},
|
|
handler: async (req, res) => {
|
|
const caCert = await server.services.certificateAuthority.getCaCertById(req.params);
|
|
|
|
res.header("Content-Type", "application/pkix-cert");
|
|
|
|
return Buffer.from(caCert.rawData);
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "PATCH",
|
|
url: "/:caId",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Update CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.UPDATE.caId)
|
|
}),
|
|
body: z.object({
|
|
status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]).optional().describe(CERTIFICATE_AUTHORITIES.UPDATE.status),
|
|
requireTemplateForIssuance: z
|
|
.boolean()
|
|
.optional()
|
|
.describe(CERTIFICATE_AUTHORITIES.CREATE.requireTemplateForIssuance)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
ca: CertificateAuthoritiesSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const ca = await server.services.certificateAuthority.updateCaById({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.UPDATE_CA,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn,
|
|
status: ca.status as CaStatus
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
ca
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "DELETE",
|
|
url: "/:caId",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Delete CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.DELETE.caId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
ca: CertificateAuthoritiesSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const ca = await server.services.certificateAuthority.deleteCaById({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.DELETE_CA,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
ca
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId/csr",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get CA CSR",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CSR.caId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
csr: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CSR.csr)
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { ca, csr } = await server.services.certificateAuthority.getCaCsr({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.GET_CA_CSR,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
csr
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "POST",
|
|
url: "/:caId/renew",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Perform CA certificate renewal",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.caId)
|
|
}),
|
|
body: z.object({
|
|
type: z.nativeEnum(CaRenewalType).describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.type),
|
|
notAfter: validateCaDateField.describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.notAfter)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.certificate),
|
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.certificateChain),
|
|
serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.serialNumber)
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { certificate, certificateChain, serialNumber, ca } =
|
|
await server.services.certificateAuthority.renewCaCert({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.RENEW_CA,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
certificate,
|
|
certificateChain,
|
|
serialNumber
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId/ca-certificates",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get list of past and current CA certificates for a CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.caId)
|
|
}),
|
|
response: {
|
|
200: z.array(
|
|
z.object({
|
|
certificate: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.certificate),
|
|
certificateChain: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.certificateChain),
|
|
serialNumber: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.serialNumber),
|
|
version: z.number().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.version)
|
|
})
|
|
)
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { caCerts, ca } = await server.services.certificateAuthority.getCaCerts({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.GET_CA_CERTS,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return caCerts;
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId/certificate", // TODO: consider updating endpoint structure considering CA certificates
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get current CA cert and cert chain of a CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT.caId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
certificate: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.certificate),
|
|
certificateChain: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.certificateChain),
|
|
serialNumber: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.serialNumber)
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { certificate, certificateChain, serialNumber, ca } = await server.services.certificateAuthority.getCaCert({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.GET_CA_CERT,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
certificate,
|
|
certificateChain,
|
|
serialNumber
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "POST",
|
|
url: "/:caId/sign-intermediate",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Create intermediate CA certificate from parent CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.caId)
|
|
}),
|
|
body: z.object({
|
|
csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.csr),
|
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.notBefore),
|
|
notAfter: validateCaDateField.describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.notAfter),
|
|
maxPathLength: z.number().min(-1).default(-1).describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.maxPathLength)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.certificate),
|
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.certificateChain),
|
|
issuingCaCertificate: z
|
|
.string()
|
|
.trim()
|
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.issuingCaCertificate),
|
|
serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.serialNumber)
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca } =
|
|
await server.services.certificateAuthority.signIntermediate({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.SIGN_INTERMEDIATE,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn,
|
|
serialNumber
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
certificate,
|
|
certificateChain,
|
|
issuingCaCertificate,
|
|
serialNumber
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "POST",
|
|
url: "/:caId/import-certificate",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Import certificate and chain to CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.IMPORT_CERT.caId)
|
|
}),
|
|
body: z.object({
|
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.IMPORT_CERT.certificate),
|
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.IMPORT_CERT.certificateChain)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
message: z.string().trim(),
|
|
caId: z.string().trim()
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { ca } = await server.services.certificateAuthority.importCertToCa({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.IMPORT_CA_CERT,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
message: "Successfully imported certificate to CA",
|
|
caId: req.params.caId
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "POST",
|
|
url: "/:caId/issue-certificate",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Issue certificate from CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.caId)
|
|
}),
|
|
body: z
|
|
.object({
|
|
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.pkiCollectionId),
|
|
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
|
|
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
|
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
|
|
ttl: z
|
|
.string()
|
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
|
|
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
const { ttl, notAfter } = data;
|
|
return (ttl !== undefined && notAfter === undefined) || (ttl === undefined && notAfter !== undefined);
|
|
},
|
|
{
|
|
message: "Either ttl or notAfter must be present, but not both",
|
|
path: ["ttl", "notAfter"]
|
|
}
|
|
),
|
|
response: {
|
|
200: z.object({
|
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificate),
|
|
issuingCaCertificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.issuingCaCertificate),
|
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateChain),
|
|
privateKey: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.privateKey),
|
|
serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.serialNumber)
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
|
|
await server.services.certificateAuthority.issueCertFromCa({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.ISSUE_CERT,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn,
|
|
serialNumber
|
|
}
|
|
}
|
|
});
|
|
|
|
await server.services.telemetry.sendPostHogEvents({
|
|
event: PostHogEventTypes.IssueCert,
|
|
distinctId: getTelemetryDistinctId(req),
|
|
properties: {
|
|
caId: ca.id,
|
|
commonName: req.body.commonName,
|
|
...req.auditLogInfo
|
|
}
|
|
});
|
|
|
|
return {
|
|
certificate,
|
|
certificateChain,
|
|
issuingCaCertificate,
|
|
privateKey,
|
|
serialNumber
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "POST",
|
|
url: "/:caId/sign-certificate",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Sign certificate from CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.caId)
|
|
}),
|
|
body: z
|
|
.object({
|
|
csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr),
|
|
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId),
|
|
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName),
|
|
commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName),
|
|
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.altNames),
|
|
ttl: z
|
|
.string()
|
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
|
|
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
const { ttl, notAfter } = data;
|
|
return (ttl !== undefined && notAfter === undefined) || (ttl === undefined && notAfter !== undefined);
|
|
},
|
|
{
|
|
message: "Either ttl or notAfter must be present, but not both",
|
|
path: ["ttl", "notAfter"]
|
|
}
|
|
),
|
|
response: {
|
|
200: z.object({
|
|
certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.certificate),
|
|
issuingCaCertificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.issuingCaCertificate),
|
|
certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateChain),
|
|
serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.serialNumber)
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca, commonName } =
|
|
await server.services.certificateAuthority.signCertFromCa({
|
|
isInternal: false,
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.SIGN_CERT,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn,
|
|
serialNumber
|
|
}
|
|
}
|
|
});
|
|
|
|
await server.services.telemetry.sendPostHogEvents({
|
|
event: PostHogEventTypes.SignCert,
|
|
distinctId: getTelemetryDistinctId(req),
|
|
properties: {
|
|
caId: ca.id,
|
|
commonName,
|
|
...req.auditLogInfo
|
|
}
|
|
});
|
|
|
|
return {
|
|
certificate: certificate.toString("pem"),
|
|
certificateChain,
|
|
issuingCaCertificate,
|
|
serialNumber
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId/certificate-templates",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get list of certificate templates for the CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.caId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
certificateTemplates: CertificateTemplatesSchema.array()
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { certificateTemplates, ca } = await server.services.certificateAuthority.getCaCertificateTemplates({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.GET_CA_CERTIFICATE_TEMPLATES,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
certificateTemplates
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/:caId/crls",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
hide: false,
|
|
tags: [ApiDocsTags.PkiCertificateAuthorities],
|
|
description: "Get list of CRLs of the CA",
|
|
params: z.object({
|
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CRLS.caId)
|
|
}),
|
|
response: {
|
|
200: z.array(
|
|
z.object({
|
|
id: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CRLS.id),
|
|
crl: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CRLS.crl)
|
|
})
|
|
)
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { ca, crls } = await server.services.certificateAuthorityCrl.getCaCrls({
|
|
caId: req.params.caId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
projectId: ca.projectId,
|
|
event: {
|
|
type: EventType.GET_CA_CRLS,
|
|
metadata: {
|
|
caId: ca.id,
|
|
dn: ca.dn
|
|
}
|
|
}
|
|
});
|
|
|
|
return crls;
|
|
}
|
|
});
|
|
|
|
// TODO: implement this endpoint in the future
|
|
// server.route({
|
|
// method: "GET",
|
|
// url: "/:caId/crl/rotate",
|
|
// config: {
|
|
// rateLimit: writeLimit
|
|
// },
|
|
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
// schema: {
|
|
// description: "Rotate CRLs of the CA",
|
|
// params: z.object({
|
|
// caId: z.string().trim()
|
|
// }),
|
|
// response: {
|
|
// 200: z.object({
|
|
// message: z.string()
|
|
// })
|
|
// }
|
|
// },
|
|
// handler: async (req) => {
|
|
// await server.services.certificateAuthority.rotateCaCrl({
|
|
// caId: req.params.caId,
|
|
// actor: req.permission.type,
|
|
// actorId: req.permission.id,
|
|
// actorAuthMethod: req.permission.authMethod,
|
|
// actorOrgId: req.permission.orgId
|
|
// });
|
|
// return {
|
|
// message: "Successfully rotated CA CRL"
|
|
// };
|
|
// }
|
|
// });
|
|
};
|