Skip private key on response if user do not have access to read

This commit is contained in:
Carlos Monastyrski
2025-12-04 21:13:43 -03:00
parent 90c73def68
commit e870b449d6
3 changed files with 119 additions and 18 deletions
@@ -225,6 +225,10 @@ describe("CertificateRequestService", () => {
{ {
action: ProjectPermissionCertificateActions.Read, action: ProjectPermissionCertificateActions.Read,
subject: ProjectPermissionSub.Certificates subject: ProjectPermissionSub.Certificates
},
{
action: ProjectPermissionCertificateActions.ReadPrivateKey,
subject: ProjectPermissionSub.Certificates
} }
]) ])
}; };
@@ -319,7 +323,7 @@ describe("CertificateRequestService", () => {
}); });
}); });
it("should get certificate from request successfully when private key access is denied", async () => { it("should get certificate from request successfully when user lacks private key permission", async () => {
const mockPermission = { const mockPermission = {
permission: createMongoAbility<ProjectPermissionSet>([ permission: createMongoAbility<ProjectPermissionSet>([
{ {
@@ -349,7 +353,6 @@ describe("CertificateRequestService", () => {
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission); (mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
(mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert); (mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert);
(mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody); (mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody);
(mockCertificateService.getCertPrivateKey as any).mockRejectedValue(new Error("Private key access denied"));
const result = await service.getCertificateFromRequest(mockGetData); const result = await service.getCertificateFromRequest(mockGetData);
@@ -363,8 +366,68 @@ describe("CertificateRequestService", () => {
actorAuthMethod: AuthMethod.EMAIL, actorAuthMethod: AuthMethod.EMAIL,
actorOrgId: "550e8400-e29b-41d4-a716-446655440002" actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
}); });
expect(mockCertificateService.getCertPrivateKey).not.toHaveBeenCalled();
expect(result).toEqual({
status: CertificateRequestStatus.ISSUED,
certificate: "-----BEGIN CERTIFICATE-----\nMOCK_CERT_PEM\n-----END CERTIFICATE-----",
privateKey: null,
serialNumber: "123456",
errorMessage: null,
createdAt: mockRequestWithCert.createdAt,
updatedAt: mockRequestWithCert.updatedAt
});
});
it("should get certificate from request successfully when user has private key permission but key retrieval fails", async () => {
const mockPermission = {
permission: createMongoAbility<ProjectPermissionSet>([
{
action: ProjectPermissionCertificateActions.Read,
subject: ProjectPermissionSub.Certificates
},
{
action: ProjectPermissionCertificateActions.ReadPrivateKey,
subject: ProjectPermissionSub.Certificates
}
])
};
const mockCertificate = {
id: "550e8400-e29b-41d4-a716-446655440009",
serialNumber: "123456",
commonName: "test.example.com"
};
const mockRequestWithCert = {
id: "550e8400-e29b-41d4-a716-446655440005",
projectId: "550e8400-e29b-41d4-a716-446655440003",
status: CertificateRequestStatus.ISSUED,
certificate: mockCertificate,
errorMessage: null,
createdAt: new Date(),
updatedAt: new Date()
};
const mockCertBody = {
certificate: "-----BEGIN CERTIFICATE-----\nMOCK_CERT_PEM\n-----END CERTIFICATE-----"
};
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
(mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert);
(mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody);
(mockCertificateService.getCertPrivateKey as any).mockRejectedValue(new Error("Private key not found"));
const result = await service.getCertificateFromRequest(mockGetData);
expect(mockCertificateRequestDAL.findByIdWithCertificate).toHaveBeenCalledWith(
"550e8400-e29b-41d4-a716-446655440005"
);
expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({
id: "550e8400-e29b-41d4-a716-446655440009",
actor: ActorType.USER,
actorId: "550e8400-e29b-41d4-a716-446655440001",
actorAuthMethod: AuthMethod.EMAIL,
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
});
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({ expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
id: "550e8400-e29b-41d4-a716-446655440008", id: "550e8400-e29b-41d4-a716-446655440009",
actor: ActorType.USER, actor: ActorType.USER,
actorId: "550e8400-e29b-41d4-a716-446655440001", actorId: "550e8400-e29b-41d4-a716-446655440001",
actorAuthMethod: AuthMethod.EMAIL, actorAuthMethod: AuthMethod.EMAIL,
@@ -218,20 +218,25 @@ export const certificateRequestServiceFactory = ({
actorOrgId actorOrgId
}); });
// Try to get private key (may fail if user doesn't have permission) const canReadPrivateKey = permission.can(
ProjectPermissionCertificateActions.ReadPrivateKey,
ProjectPermissionSub.Certificates
);
let privateKey: string | null = null; let privateKey: string | null = null;
try { if (canReadPrivateKey) {
const certPrivateKey = await certificateService.getCertPrivateKey({ try {
id: certificateRequest.certificate.id, const certPrivateKey = await certificateService.getCertPrivateKey({
actor, id: certificateRequest.certificate.id,
actorId, actor,
actorAuthMethod, actorId,
actorOrgId actorAuthMethod,
}); actorOrgId
privateKey = certPrivateKey.certPrivateKey; });
} catch (error) { privateKey = certPrivateKey.certPrivateKey;
// Private key access denied - continue without it } catch (error) {
privateKey = null; privateKey = null;
}
} }
return { return {
@@ -1001,11 +1001,27 @@ export const certificateV3ServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: profile.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
const canReadPrivateKey = permission.can(
ProjectPermissionCertificateActions.ReadPrivateKey,
ProjectPermissionSub.Certificates
);
const privateKeyForResponse = canReadPrivateKey ? selfSignedResult.privateKey.toString("utf8") : undefined;
return { return {
certificate: selfSignedResult.certificate.toString("utf8"), certificate: selfSignedResult.certificate.toString("utf8"),
issuingCaCertificate: "", issuingCaCertificate: "",
certificateChain: selfSignedResult.certificate.toString("utf8"), certificateChain: selfSignedResult.certificate.toString("utf8"),
privateKey: selfSignedResult.privateKey.toString("utf8"), privateKey: privateKeyForResponse,
serialNumber: selfSignedResult.serialNumber, serialNumber: selfSignedResult.serialNumber,
certificateId: certificateData.id, certificateId: certificateData.id,
certificateRequestId, certificateRequestId,
@@ -1102,11 +1118,28 @@ export const certificateV3ServiceFactory = ({
finalCertificateChain = removeRootCaFromChain(finalCertificateChain); finalCertificateChain = removeRootCaFromChain(finalCertificateChain);
} }
// Check if user has permission to read private key
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: profile.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
const canReadPrivateKey = permission.can(
ProjectPermissionCertificateActions.ReadPrivateKey,
ProjectPermissionSub.Certificates
);
const privateKeyForResponse = canReadPrivateKey ? bufferToString(privateKey) : undefined;
return { return {
certificate: bufferToString(certificate), certificate: bufferToString(certificate),
issuingCaCertificate: bufferToString(issuingCaCertificate), issuingCaCertificate: bufferToString(issuingCaCertificate),
certificateChain: finalCertificateChain, certificateChain: finalCertificateChain,
privateKey: bufferToString(privateKey), privateKey: privateKeyForResponse,
serialNumber, serialNumber,
certificateId: cert.id, certificateId: cert.id,
certificateRequestId, certificateRequestId,