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

View File

@@ -225,6 +225,10 @@ describe("CertificateRequestService", () => {
{
action: ProjectPermissionCertificateActions.Read,
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 = {
permission: createMongoAbility<ProjectPermissionSet>([
{
@@ -349,7 +353,6 @@ describe("CertificateRequestService", () => {
(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 access denied"));
const result = await service.getCertificateFromRequest(mockGetData);
@@ -363,8 +366,68 @@ describe("CertificateRequestService", () => {
actorAuthMethod: AuthMethod.EMAIL,
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({
id: "550e8400-e29b-41d4-a716-446655440008",
id: "550e8400-e29b-41d4-a716-446655440009",
actor: ActorType.USER,
actorId: "550e8400-e29b-41d4-a716-446655440001",
actorAuthMethod: AuthMethod.EMAIL,

View File

@@ -218,20 +218,25 @@ export const certificateRequestServiceFactory = ({
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;
try {
const certPrivateKey = await certificateService.getCertPrivateKey({
id: certificateRequest.certificate.id,
actor,
actorId,
actorAuthMethod,
actorOrgId
});
privateKey = certPrivateKey.certPrivateKey;
} catch (error) {
// Private key access denied - continue without it
privateKey = null;
if (canReadPrivateKey) {
try {
const certPrivateKey = await certificateService.getCertPrivateKey({
id: certificateRequest.certificate.id,
actor,
actorId,
actorAuthMethod,
actorOrgId
});
privateKey = certPrivateKey.certPrivateKey;
} catch (error) {
privateKey = null;
}
}
return {

View File

@@ -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 {
certificate: selfSignedResult.certificate.toString("utf8"),
issuingCaCertificate: "",
certificateChain: selfSignedResult.certificate.toString("utf8"),
privateKey: selfSignedResult.privateKey.toString("utf8"),
privateKey: privateKeyForResponse,
serialNumber: selfSignedResult.serialNumber,
certificateId: certificateData.id,
certificateRequestId,
@@ -1102,11 +1118,28 @@ export const certificateV3ServiceFactory = ({
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 {
certificate: bufferToString(certificate),
issuingCaCertificate: bufferToString(issuingCaCertificate),
certificateChain: finalCertificateChain,
privateKey: bufferToString(privateKey),
privateKey: privateKeyForResponse,
serialNumber,
certificateId: cert.id,
certificateRequestId,