Implement acme secret reveal

This commit is contained in:
Fang-Pen Lin
2025-11-07 09:20:03 -08:00
parent 6c6c5f803d
commit fd6128aa65
6 changed files with 94 additions and 6 deletions
@@ -20,5 +20,3 @@ Feature: ACME Cert Profile
Then the value response with jq .certificateProfile.caId should be equal to "{CERT_CA_ID}" Then the value response with jq .certificateProfile.caId should be equal to "{CERT_CA_ID}"
Then the value response with jq .certificateProfile.certificateTemplateId should be equal to "{CERT_TEMPLATE_ID}" Then the value response with jq .certificateProfile.certificateTemplateId should be equal to "{CERT_TEMPLATE_ID}"
Then the value response with jq .certificateProfile.enrollmentType should be equal to "acme" Then the value response with jq .certificateProfile.enrollmentType should be equal to "acme"
Then the value response with jq .certificateProfile.eabKid should be present
Then the value response with jq .certificateProfile.eabSecret should be present
@@ -106,7 +106,9 @@ const buildAdminPermissionRules = () => {
ProjectPermissionCertificateProfileActions.Edit, ProjectPermissionCertificateProfileActions.Edit,
ProjectPermissionCertificateProfileActions.Create, ProjectPermissionCertificateProfileActions.Create,
ProjectPermissionCertificateProfileActions.Delete, ProjectPermissionCertificateProfileActions.Delete,
ProjectPermissionCertificateProfileActions.IssueCert ProjectPermissionCertificateProfileActions.IssueCert,
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
ProjectPermissionCertificateProfileActions.RotateAcmeEabSecret
], ],
ProjectPermissionSub.CertificateProfiles ProjectPermissionSub.CertificateProfiles
); );
@@ -116,7 +116,9 @@ export enum ProjectPermissionCertificateProfileActions {
Create = "create", Create = "create",
Edit = "edit", Edit = "edit",
Delete = "delete", Delete = "delete",
IssueCert = "issue-cert" IssueCert = "issue-cert",
RevealAcmeEabSecret = "reveal-acme-eab-secret",
RotateAcmeEabSecret = "rotate-acme-eab-secret"
} }
export enum ProjectPermissionSecretSyncActions { export enum ProjectPermissionSecretSyncActions {
@@ -7,8 +7,8 @@ import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertStatus } from "@app/services/certificate/certificate-types";
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
import { CertStatus } from "@app/services/certificate/certificate-types";
export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => { export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
@@ -491,4 +491,35 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
return { certificates }; return { certificates };
} }
}); });
server.route({
method: "GET",
url: "/:id/acme/eab-secret/reveal",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
id: z.string().uuid()
}),
response: {
200: z.object({
eabSecret: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const eabSecret = await server.services.certificateProfile.revealAcmeEabSecret({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id
});
return { eabSecret };
}
});
}; };
@@ -782,6 +782,59 @@ export const certificateProfileServiceFactory = ({
}; };
}; };
const revealAcmeEabSecret = async ({
actor,
actorId,
actorAuthMethod,
actorOrgId,
profileId
}: {
actor: ActorType;
actorId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string;
profileId: string;
}) => {
const profile = await certificateProfileDAL.findByIdWithConfigs(profileId);
if (!profile) {
throw new NotFoundError({ message: "Certificate profile not found" });
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: profile.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
ProjectPermissionSub.CertificateProfiles
);
if (profile.enrollmentType !== EnrollmentType.ACME) {
throw new ForbiddenRequestError({
message: "Profile is not configured for ACME enrollment"
});
}
if (!profile.acmeConfig) {
throw new NotFoundError({ message: "ACME configuration not found for this profile" });
}
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
projectId: profile.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig.encryptedEabSecret });
return eabSecret.toString();
};
return { return {
createProfile, createProfile,
updateProfile, updateProfile,
@@ -791,6 +844,7 @@ export const certificateProfileServiceFactory = ({
listProfiles, listProfiles,
deleteProfile, deleteProfile,
getProfileCertificates, getProfileCertificates,
getEstConfigurationByProfile getEstConfigurationByProfile,
revealAcmeEabSecret
}; };
}; };
@@ -58,6 +58,7 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & {
}; };
acmeConfig?: { acmeConfig?: {
id: string; id: string;
encryptedEabSecret: Buffer;
}; };
metrics?: TCertificateProfileMetrics; metrics?: TCertificateProfileMetrics;
}; };