mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 02:27:39 +00:00
feat: added subscriber endpoint for fetching active cert
This commit is contained in:
@@ -263,6 +263,7 @@ export enum EventType {
|
|||||||
ISSUE_PKI_SUBSCRIBER_CERT = "issue-pki-subscriber-cert",
|
ISSUE_PKI_SUBSCRIBER_CERT = "issue-pki-subscriber-cert",
|
||||||
SIGN_PKI_SUBSCRIBER_CERT = "sign-pki-subscriber-cert",
|
SIGN_PKI_SUBSCRIBER_CERT = "sign-pki-subscriber-cert",
|
||||||
LIST_PKI_SUBSCRIBER_CERTS = "list-pki-subscriber-certs",
|
LIST_PKI_SUBSCRIBER_CERTS = "list-pki-subscriber-certs",
|
||||||
|
GET_SUBSCRIBER_ACTIVE_CERT_BUNDLE = "get-subscriber-active-cert-bundle",
|
||||||
CREATE_KMS = "create-kms",
|
CREATE_KMS = "create-kms",
|
||||||
UPDATE_KMS = "update-kms",
|
UPDATE_KMS = "update-kms",
|
||||||
DELETE_KMS = "delete-kms",
|
DELETE_KMS = "delete-kms",
|
||||||
@@ -2061,6 +2062,16 @@ interface ListPkiSubscriberCerts {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetSubscriberActiveCertBundle {
|
||||||
|
type: EventType.GET_SUBSCRIBER_ACTIVE_CERT_BUNDLE;
|
||||||
|
metadata: {
|
||||||
|
subscriberId: string;
|
||||||
|
subscriberName: string;
|
||||||
|
certId: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateKmsEvent {
|
interface CreateKmsEvent {
|
||||||
type: EventType.CREATE_KMS;
|
type: EventType.CREATE_KMS;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -3033,6 +3044,7 @@ export type Event =
|
|||||||
| IssuePkiSubscriberCert
|
| IssuePkiSubscriberCert
|
||||||
| SignPkiSubscriberCert
|
| SignPkiSubscriberCert
|
||||||
| ListPkiSubscriberCerts
|
| ListPkiSubscriberCerts
|
||||||
|
| GetSubscriberActiveCertBundle
|
||||||
| CreateKmsEvent
|
| CreateKmsEvent
|
||||||
| UpdateKmsEvent
|
| UpdateKmsEvent
|
||||||
| DeleteKmsEvent
|
| DeleteKmsEvent
|
||||||
|
|||||||
@@ -1755,6 +1755,14 @@ export const PKI_SUBSCRIBERS = {
|
|||||||
subscriberName: "The name of the PKI subscriber to get.",
|
subscriberName: "The name of the PKI subscriber to get.",
|
||||||
projectId: "The ID of the project to get the PKI subscriber for."
|
projectId: "The ID of the project to get the PKI subscriber for."
|
||||||
},
|
},
|
||||||
|
GET_ACTIVE_CERT_BUNDLE: {
|
||||||
|
subscriberName: "The name of the PKI subscriber to get the active certificate bundle for.",
|
||||||
|
projectId: "The ID of the project to get the active certificate bundle for.",
|
||||||
|
certificate: "The active certificate for the subscriber.",
|
||||||
|
certificateChain: "The certificate chain of the active certificate for the subscriber.",
|
||||||
|
privateKey: "The private key of the active certificate for the subscriber.",
|
||||||
|
serialNumber: "The serial number of the active certificate for the subscriber."
|
||||||
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the PKI subscriber in.",
|
projectId: "The ID of the project to create the PKI subscriber in.",
|
||||||
caId: "The ID of the CA that will issue certificates for the PKI subscriber.",
|
caId: "The ID of the CA that will issue certificates for the PKI subscriber.",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|||||||
import { ApiDocsTags, PKI_SUBSCRIBERS } from "@app/lib/api-docs";
|
import { ApiDocsTags, PKI_SUBSCRIBERS } from "@app/lib/api-docs";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { addNoCacheHeaders } from "@app/server/lib/caching";
|
||||||
import { slugSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -482,6 +483,72 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:subscriberName/active-certificate/bundle",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Get active certificate bundle of a subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberName: z.string().describe(PKI_SUBSCRIBERS.GET_ACTIVE_CERT_BUNDLE.subscriberName)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.GET_ACTIVE_CERT_BUNDLE.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.GET_ACTIVE_CERT_BUNDLE.certificate),
|
||||||
|
certificateChain: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.nullable()
|
||||||
|
.describe(PKI_SUBSCRIBERS.GET_ACTIVE_CERT_BUNDLE.certificateChain),
|
||||||
|
privateKey: z.string().trim().describe(PKI_SUBSCRIBERS.GET_ACTIVE_CERT_BUNDLE.privateKey),
|
||||||
|
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.GET_ACTIVE_CERT_BUNDLE.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req, reply) => {
|
||||||
|
const { certificate, certificateChain, serialNumber, cert, privateKey, subscriber } =
|
||||||
|
await server.services.pkiSubscriber.getSubscriberActiveCertBundle({
|
||||||
|
subscriberName: req.params.subscriberName,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: cert.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_SUBSCRIBER_ACTIVE_CERT_BUNDLE,
|
||||||
|
metadata: {
|
||||||
|
subscriberId: subscriber.id,
|
||||||
|
subscriberName: subscriber.name,
|
||||||
|
certId: cert.id,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addNoCacheHeaders(reply);
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
serialNumber,
|
||||||
|
privateKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:subscriberName/certificates",
|
url: "/:subscriberName/certificates",
|
||||||
|
|||||||
@@ -3,11 +3,24 @@ import { TableName } from "@app/db/schemas";
|
|||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { CertStatus } from "./certificate-types";
|
||||||
|
|
||||||
export type TCertificateDALFactory = ReturnType<typeof certificateDALFactory>;
|
export type TCertificateDALFactory = ReturnType<typeof certificateDALFactory>;
|
||||||
|
|
||||||
export const certificateDALFactory = (db: TDbClient) => {
|
export const certificateDALFactory = (db: TDbClient) => {
|
||||||
const certificateOrm = ormify(db, TableName.Certificate);
|
const certificateOrm = ormify(db, TableName.Certificate);
|
||||||
|
|
||||||
|
const findLatestActiveCertForSubscriber = async ({ subscriberId }: { subscriberId: string }) => {
|
||||||
|
const cert = await db
|
||||||
|
.replicaNode()(TableName.Certificate)
|
||||||
|
.where({ pkiSubscriberId: subscriberId, status: CertStatus.ACTIVE })
|
||||||
|
.where("notAfter", ">", new Date())
|
||||||
|
.orderBy("notBefore", "desc")
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return cert;
|
||||||
|
};
|
||||||
|
|
||||||
const countCertificatesInProject = async ({
|
const countCertificatesInProject = async ({
|
||||||
projectId,
|
projectId,
|
||||||
friendlyName,
|
friendlyName,
|
||||||
@@ -65,6 +78,7 @@ export const certificateDALFactory = (db: TDbClient) => {
|
|||||||
return {
|
return {
|
||||||
...certificateOrm,
|
...certificateOrm,
|
||||||
countCertificatesInProject,
|
countCertificatesInProject,
|
||||||
countCertificatesForPkiSubscriber
|
countCertificatesForPkiSubscriber,
|
||||||
|
findLatestActiveCertForSubscriber
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ActionProjectType } from "@app/db/schemas";
|
|||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
|
ProjectPermissionCertificateActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
@@ -38,6 +39,7 @@ import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subsc
|
|||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
|
import { getCertificateCredentials } from "../certificate/certificate-fns";
|
||||||
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
import { TCertificateAuthorityQueueFactory } from "../certificate-authority/certificate-authority-queue";
|
import { TCertificateAuthorityQueueFactory } from "../certificate-authority/certificate-authority-queue";
|
||||||
import { InternalCertificateAuthorityFns } from "../certificate-authority/internal/internal-certificate-authority-fns";
|
import { InternalCertificateAuthorityFns } from "../certificate-authority/internal/internal-certificate-authority-fns";
|
||||||
@@ -46,6 +48,7 @@ import {
|
|||||||
TCreatePkiSubscriberDTO,
|
TCreatePkiSubscriberDTO,
|
||||||
TDeletePkiSubscriberDTO,
|
TDeletePkiSubscriberDTO,
|
||||||
TGetPkiSubscriberDTO,
|
TGetPkiSubscriberDTO,
|
||||||
|
TGetSubscriberActiveCertBundleDTO,
|
||||||
TIssuePkiSubscriberCertDTO,
|
TIssuePkiSubscriberCertDTO,
|
||||||
TListPkiSubscriberCertsDTO,
|
TListPkiSubscriberCertsDTO,
|
||||||
TOrderPkiSubscriberCertDTO,
|
TOrderPkiSubscriberCertDTO,
|
||||||
@@ -66,9 +69,12 @@ type TPkiSubscriberServiceFactoryDep = {
|
|||||||
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
certificateAuthorityQueue: Pick<TCertificateAuthorityQueueFactory, "orderCertificateForSubscriber">;
|
certificateAuthorityQueue: Pick<TCertificateAuthorityQueueFactory, "orderCertificateForSubscriber">;
|
||||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction" | "countCertificatesForPkiSubscriber" | "find">;
|
certificateDAL: Pick<
|
||||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
TCertificateDALFactory,
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
"create" | "transaction" | "countCertificatesForPkiSubscriber" | "findLatestActiveCertForSubscriber" | "find"
|
||||||
|
>;
|
||||||
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create" | "findOne">;
|
||||||
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create" | "findOne">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction" | "findById" | "find">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction" | "findById" | "find">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -691,6 +697,110 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSubscriberActiveCertBundle = async ({
|
||||||
|
subscriberName,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetSubscriberActiveCertBundleDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findOne({
|
||||||
|
name: subscriberName,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.ListCerts,
|
||||||
|
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||||
|
name: subscriber.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionCertificateActions.Read,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionCertificateActions.ReadPrivateKey,
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
|
const cert = await certificateDAL.findLatestActiveCertForSubscriber({
|
||||||
|
subscriberId: subscriber.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!cert) {
|
||||||
|
throw new NotFoundError({ message: "No active certificate found for subscriber" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||||
|
|
||||||
|
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: cert.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKeyId
|
||||||
|
});
|
||||||
|
const decryptedCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: certBody.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const certObj = new x509.X509Certificate(decryptedCert);
|
||||||
|
const certificate = certObj.toString("pem");
|
||||||
|
|
||||||
|
let certificateChain = null;
|
||||||
|
|
||||||
|
// On newer certs the certBody.encryptedCertificateChain column will always exist.
|
||||||
|
// Older certs will have a caCertId which will be used as a fallback mechanism for structuring the chain.
|
||||||
|
if (certBody.encryptedCertificateChain) {
|
||||||
|
const decryptedCertChain = await kmsDecryptor({
|
||||||
|
cipherTextBlob: certBody.encryptedCertificateChain
|
||||||
|
});
|
||||||
|
certificateChain = decryptedCertChain.toString();
|
||||||
|
} else if (cert.caCertId) {
|
||||||
|
const { caCert, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: cert.caCertId,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
certificateChain = `${caCert}\n${caCertChain}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { certPrivateKey } = await getCertificateCredentials({
|
||||||
|
certId: cert.id,
|
||||||
|
projectId: cert.projectId,
|
||||||
|
certificateSecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
privateKey: certPrivateKey,
|
||||||
|
serialNumber: cert.serialNumber,
|
||||||
|
cert,
|
||||||
|
subscriber
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createSubscriber,
|
createSubscriber,
|
||||||
getSubscriber,
|
getSubscriber,
|
||||||
@@ -699,6 +809,7 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
issueSubscriberCert,
|
issueSubscriberCert,
|
||||||
signSubscriberCert,
|
signSubscriberCert,
|
||||||
listSubscriberCerts,
|
listSubscriberCerts,
|
||||||
orderSubscriberCert
|
orderSubscriberCert,
|
||||||
|
getSubscriberActiveCertBundle
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export type TListPkiSubscriberCertsDTO = {
|
|||||||
limit: number;
|
limit: number;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetSubscriberActiveCertBundleDTO = {
|
||||||
|
subscriberName: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
export enum SubscriberOperationStatus {
|
export enum SubscriberOperationStatus {
|
||||||
SUCCESS = "success",
|
SUCCESS = "success",
|
||||||
FAILED = "failed"
|
FAILED = "failed"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Retrieve active certificate bundle"
|
||||||
|
openapi: "GET /api/v1/pki/subscribers/{subscriberName}/active-certificate/bundle"
|
||||||
|
---
|
||||||
+2
-1
@@ -1468,7 +1468,8 @@
|
|||||||
"api-reference/endpoints/pki/subscribers/update",
|
"api-reference/endpoints/pki/subscribers/update",
|
||||||
"api-reference/endpoints/pki/subscribers/delete",
|
"api-reference/endpoints/pki/subscribers/delete",
|
||||||
"api-reference/endpoints/pki/subscribers/issue-cert",
|
"api-reference/endpoints/pki/subscribers/issue-cert",
|
||||||
"api-reference/endpoints/pki/subscribers/sign-cert"
|
"api-reference/endpoints/pki/subscribers/sign-cert",
|
||||||
|
"api-reference/endpoints/pki/subscribers/get-active-cert-bundle"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user