Implement acme secret reveal

This commit is contained in:
Fang-Pen Lin
2025-11-03 14:11:01 -08:00
parent 6c6c5f803d
commit fd6128aa65
6 changed files with 94 additions and 6 deletions

View File

@@ -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.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.eabKid should be present
Then the value response with jq .certificateProfile.eabSecret should be present

View File

@@ -106,7 +106,9 @@ const buildAdminPermissionRules = () => {
ProjectPermissionCertificateProfileActions.Edit,
ProjectPermissionCertificateProfileActions.Create,
ProjectPermissionCertificateProfileActions.Delete,
ProjectPermissionCertificateProfileActions.IssueCert
ProjectPermissionCertificateProfileActions.IssueCert,
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
ProjectPermissionCertificateProfileActions.RotateAcmeEabSecret
],
ProjectPermissionSub.CertificateProfiles
);

View File

@@ -116,7 +116,9 @@ export enum ProjectPermissionCertificateProfileActions {
Create = "create",
Edit = "edit",
Delete = "delete",
IssueCert = "issue-cert"
IssueCert = "issue-cert",
RevealAcmeEabSecret = "reveal-acme-eab-secret",
RotateAcmeEabSecret = "rotate-acme-eab-secret"
}
export enum ProjectPermissionSecretSyncActions {

View File

@@ -7,8 +7,8 @@ import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
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 { CertStatus } from "@app/services/certificate/certificate-types";
export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -491,4 +491,35 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
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 };
}
});
};

View File

@@ -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 {
createProfile,
updateProfile,
@@ -791,6 +844,7 @@ export const certificateProfileServiceFactory = ({
listProfiles,
deleteProfile,
getProfileCertificates,
getEstConfigurationByProfile
getEstConfigurationByProfile,
revealAcmeEabSecret
};
};

View File

@@ -58,6 +58,7 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & {
};
acmeConfig?: {
id: string;
encryptedEabSecret: Buffer;
};
metrics?: TCertificateProfileMetrics;
};