mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 18:29:28 +00:00
825 lines
25 KiB
TypeScript
825 lines
25 KiB
TypeScript
import { ForbiddenError } from "@casl/ability";
|
|
import * as x509 from "@peculiar/x509";
|
|
|
|
import { ActionProjectType } from "@app/db/schemas";
|
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
|
import {
|
|
ProjectPermissionCertificateProfileActions,
|
|
ProjectPermissionSub
|
|
} from "@app/ee/services/permission/project-permission";
|
|
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
|
import { getConfig } from "@app/lib/config/env";
|
|
import { crypto } from "@app/lib/crypto/cryptography";
|
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
|
|
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
|
import { isCertChainValid } from "../certificate/certificate-fns";
|
|
import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal";
|
|
import { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal";
|
|
import { TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types";
|
|
import { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal";
|
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
|
import { TProjectDALFactory } from "../project/project-dal";
|
|
import { getProjectKmsCertificateKeyId } from "../project/project-fns";
|
|
import { TCertificateProfileDALFactory } from "./certificate-profile-dal";
|
|
import {
|
|
EnrollmentType,
|
|
TCertificateProfile,
|
|
TCertificateProfileCertificate,
|
|
TCertificateProfileInsert,
|
|
TCertificateProfileMetrics,
|
|
TCertificateProfileUpdate,
|
|
TCertificateProfileWithConfigs,
|
|
TCertificateProfileWithRawMetrics
|
|
} from "./certificate-profile-types";
|
|
|
|
const validateAndEncryptPemCaChain = async (
|
|
caChain: string,
|
|
projectId: string,
|
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey">,
|
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">
|
|
) => {
|
|
try {
|
|
const certificates = extractX509CertFromChain(caChain)?.map((cert) => new x509.X509Certificate(cert));
|
|
|
|
if (!certificates || certificates.length === 0) {
|
|
throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
|
}
|
|
|
|
if (!(await isCertChainValid(certificates))) {
|
|
throw new BadRequestError({ message: "Invalid certificate chain" });
|
|
}
|
|
|
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
|
projectId,
|
|
projectDAL,
|
|
kmsService
|
|
});
|
|
|
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
|
kmsId: certificateManagerKmsId
|
|
});
|
|
|
|
const { cipherTextBlob } = await kmsEncryptor({
|
|
plainText: Buffer.from(caChain)
|
|
});
|
|
|
|
return { encryptedCaChain: cipherTextBlob };
|
|
} catch (error) {
|
|
throw new BadRequestError({ message: `Failed to process certificate chain: ${(error as Error).message}` });
|
|
}
|
|
};
|
|
|
|
const decryptCaChain = async (
|
|
encryptedCaChain: Buffer,
|
|
projectId: string,
|
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey">,
|
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">
|
|
): Promise<string> => {
|
|
try {
|
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
|
projectId,
|
|
projectDAL,
|
|
kmsService
|
|
});
|
|
|
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
|
kmsId: certificateManagerKmsId
|
|
});
|
|
|
|
const decryptedCaChain = await kmsDecryptor({
|
|
cipherTextBlob: encryptedCaChain
|
|
});
|
|
|
|
return decryptedCaChain.toString();
|
|
} catch (error) {
|
|
throw new BadRequestError({ message: `Failed to decrypt certificate chain: ${(error as Error).message}` });
|
|
}
|
|
};
|
|
|
|
export type TCertificateProfileCreateData = Omit<TCertificateProfileInsert, "estConfigId" | "apiConfigId"> & {
|
|
estConfig?: TEstConfigData;
|
|
apiConfig?: TApiConfigData;
|
|
};
|
|
|
|
type TCertificateProfileServiceFactoryDep = {
|
|
certificateProfileDAL: TCertificateProfileDALFactory;
|
|
certificateTemplateV2DAL: TCertificateTemplateV2DALFactory;
|
|
apiEnrollmentConfigDAL: TApiEnrollmentConfigDALFactory;
|
|
estEnrollmentConfigDAL: TEstEnrollmentConfigDALFactory;
|
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
|
};
|
|
|
|
export type TCertificateProfileServiceFactory = ReturnType<typeof certificateProfileServiceFactory>;
|
|
|
|
const convertDalToService = (dalResult: Record<string, unknown>): TCertificateProfile => {
|
|
return {
|
|
...dalResult,
|
|
enrollmentType: dalResult.enrollmentType as EnrollmentType
|
|
} as TCertificateProfile;
|
|
};
|
|
|
|
export const certificateProfileServiceFactory = ({
|
|
certificateProfileDAL,
|
|
certificateTemplateV2DAL,
|
|
apiEnrollmentConfigDAL,
|
|
estEnrollmentConfigDAL,
|
|
permissionService,
|
|
kmsService,
|
|
projectDAL
|
|
}: TCertificateProfileServiceFactoryDep) => {
|
|
const createProfile = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
projectId,
|
|
data
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
projectId: string;
|
|
data: Omit<TCertificateProfileCreateData, "projectId">;
|
|
}): Promise<TCertificateProfile> => {
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.CertificateManager
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionCertificateProfileActions.Create,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
// Validate that certificate template exists and belongs to the same project
|
|
if (data.certificateTemplateId) {
|
|
const template = await certificateTemplateV2DAL.findById(data.certificateTemplateId);
|
|
if (!template) {
|
|
throw new NotFoundError({ message: "Certificate template not found" });
|
|
}
|
|
if (template.projectId !== projectId) {
|
|
throw new ForbiddenRequestError({
|
|
message: "Certificate template must belong to the same project"
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for slug uniqueness within project
|
|
const existingSlugProfile = await certificateProfileDAL.findBySlugAndProjectId(data.slug, projectId);
|
|
if (existingSlugProfile) {
|
|
throw new ForbiddenRequestError({
|
|
message: "Certificate profile with this name already exists in project"
|
|
});
|
|
}
|
|
|
|
// Validate enrollment configuration requirements
|
|
if (data.enrollmentType === EnrollmentType.EST && !data.estConfig) {
|
|
throw new ForbiddenRequestError({
|
|
message: "EST enrollment requires EST configuration"
|
|
});
|
|
}
|
|
if (data.enrollmentType === EnrollmentType.API && !data.apiConfig) {
|
|
throw new ForbiddenRequestError({
|
|
message: "API enrollment requires API configuration"
|
|
});
|
|
}
|
|
|
|
// Create enrollment configs and profile
|
|
const profile = await certificateProfileDAL.transaction(async (tx) => {
|
|
let estConfigId: string | null = null;
|
|
let apiConfigId: string | null = null;
|
|
|
|
if (data.enrollmentType === EnrollmentType.EST && data.estConfig) {
|
|
const appCfg = getConfig();
|
|
// Hash the passphrase
|
|
const hashedPassphrase = await crypto.hashing().createHash(data.estConfig.passphrase, appCfg.SALT_ROUNDS);
|
|
|
|
let encryptedCaChainBuffer: Buffer | null = null;
|
|
if (!data.estConfig.disableBootstrapCaValidation && data.estConfig.caChain) {
|
|
const { encryptedCaChain } = await validateAndEncryptPemCaChain(
|
|
data.estConfig.caChain,
|
|
projectId,
|
|
kmsService,
|
|
projectDAL
|
|
);
|
|
encryptedCaChainBuffer = encryptedCaChain;
|
|
}
|
|
|
|
const estConfig = await estEnrollmentConfigDAL.create(
|
|
{
|
|
disableBootstrapCaValidation: data.estConfig.disableBootstrapCaValidation,
|
|
hashedPassphrase,
|
|
encryptedCaChain: encryptedCaChainBuffer
|
|
},
|
|
tx
|
|
);
|
|
estConfigId = estConfig.id;
|
|
} else if (data.enrollmentType === EnrollmentType.API && data.apiConfig) {
|
|
const apiConfig = await apiEnrollmentConfigDAL.create(
|
|
{
|
|
autoRenew: data.apiConfig.autoRenew,
|
|
autoRenewDays: data.apiConfig.autoRenewDays
|
|
},
|
|
tx
|
|
);
|
|
apiConfigId = apiConfig.id;
|
|
}
|
|
|
|
// Create the profile with the created config IDs
|
|
const { estConfig, apiConfig, ...profileData } = data;
|
|
const profileResult = await certificateProfileDAL.create(
|
|
{
|
|
...profileData,
|
|
projectId,
|
|
estConfigId,
|
|
apiConfigId
|
|
},
|
|
tx
|
|
);
|
|
|
|
return profileResult;
|
|
});
|
|
|
|
return convertDalToService(profile);
|
|
};
|
|
|
|
const updateProfile = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
profileId,
|
|
data
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
profileId: string;
|
|
data: TCertificateProfileUpdate;
|
|
}): Promise<TCertificateProfile> => {
|
|
const existingProfile = await certificateProfileDAL.findById(profileId);
|
|
if (!existingProfile) {
|
|
throw new NotFoundError({ message: "Certificate profile not found" });
|
|
}
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: existingProfile.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.CertificateManager
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionCertificateProfileActions.Edit,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
if (data.certificateTemplateId) {
|
|
const template = await certificateTemplateV2DAL.findById(data.certificateTemplateId);
|
|
if (!template) {
|
|
throw new NotFoundError({ message: "Certificate template not found" });
|
|
}
|
|
if (template.projectId !== existingProfile.projectId) {
|
|
throw new ForbiddenRequestError({
|
|
message: "Certificate template must belong to the same project"
|
|
});
|
|
}
|
|
}
|
|
|
|
if (data.slug && data.slug !== existingProfile.slug) {
|
|
const conflictingProfile = await certificateProfileDAL.findBySlugAndProjectId(
|
|
data.slug,
|
|
existingProfile.projectId
|
|
);
|
|
if (conflictingProfile && conflictingProfile.id !== profileId) {
|
|
throw new ForbiddenRequestError({
|
|
message: "Certificate profile with this name already exists in project"
|
|
});
|
|
}
|
|
}
|
|
|
|
const { estConfig, apiConfig, ...profileUpdateData } = data;
|
|
|
|
const updatedProfile = await certificateProfileDAL.transaction(async (tx) => {
|
|
if (estConfig && existingProfile.estConfigId) {
|
|
const updateData: {
|
|
disableBootstrapCaValidation: boolean;
|
|
hashedPassphrase?: string;
|
|
encryptedCaChain?: Buffer;
|
|
} = {
|
|
disableBootstrapCaValidation: estConfig.disableBootstrapCaValidation ?? false
|
|
};
|
|
|
|
if (estConfig.passphrase) {
|
|
updateData.hashedPassphrase = await crypto
|
|
.hashing()
|
|
.createHash(estConfig.passphrase, getConfig().SALT_ROUNDS);
|
|
}
|
|
|
|
if (estConfig.caChain) {
|
|
const { encryptedCaChain } = await validateAndEncryptPemCaChain(
|
|
estConfig.caChain,
|
|
existingProfile.projectId,
|
|
kmsService,
|
|
projectDAL
|
|
);
|
|
updateData.encryptedCaChain = encryptedCaChain;
|
|
}
|
|
|
|
await estEnrollmentConfigDAL.updateById(existingProfile.estConfigId, updateData, tx);
|
|
}
|
|
|
|
if (apiConfig && existingProfile.apiConfigId) {
|
|
await apiEnrollmentConfigDAL.updateById(
|
|
existingProfile.apiConfigId,
|
|
{
|
|
autoRenew: apiConfig.autoRenew,
|
|
autoRenewDays: apiConfig.autoRenewDays
|
|
},
|
|
tx
|
|
);
|
|
}
|
|
|
|
const profileResult = await certificateProfileDAL.updateById(profileId, profileUpdateData, tx);
|
|
return profileResult;
|
|
});
|
|
|
|
return convertDalToService(updatedProfile);
|
|
};
|
|
|
|
const getProfileById = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
profileId,
|
|
includeMetrics = false,
|
|
expiringDays = 30
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
profileId: string;
|
|
includeMetrics?: boolean;
|
|
expiringDays?: number;
|
|
}): Promise<TCertificateProfile & { metrics?: TCertificateProfileMetrics }> => {
|
|
const profile = await certificateProfileDAL.findById(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.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const converted = convertDalToService(profile);
|
|
|
|
if (includeMetrics) {
|
|
const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays);
|
|
return {
|
|
...converted,
|
|
metrics
|
|
};
|
|
}
|
|
|
|
return converted;
|
|
};
|
|
|
|
const getProfileByIdWithConfigs = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
profileId
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
profileId: string;
|
|
}): Promise<TCertificateProfileWithConfigs> => {
|
|
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.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
if (profile.estConfig && profile.estConfig.caChain) {
|
|
try {
|
|
const estConfig = await estEnrollmentConfigDAL.findById(profile.estConfigId!);
|
|
if (estConfig && estConfig.encryptedCaChain) {
|
|
const decryptedCaChain = await decryptCaChain(
|
|
estConfig.encryptedCaChain,
|
|
profile.projectId,
|
|
kmsService,
|
|
projectDAL
|
|
);
|
|
profile.estConfig.caChain = decryptedCaChain;
|
|
} else {
|
|
profile.estConfig.caChain = "";
|
|
}
|
|
} catch (error) {
|
|
profile.estConfig.caChain = "";
|
|
}
|
|
}
|
|
|
|
return {
|
|
...profile,
|
|
enrollmentType: profile.enrollmentType as EnrollmentType
|
|
};
|
|
};
|
|
|
|
const getProfileBySlug = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
projectId,
|
|
slug
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
projectId: string;
|
|
slug: string;
|
|
}): Promise<TCertificateProfile> => {
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.CertificateManager
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionCertificateProfileActions.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const profile = await certificateProfileDAL.findBySlugAndProjectId(slug, projectId);
|
|
if (!profile) {
|
|
throw new NotFoundError({ message: "Certificate profile not found" });
|
|
}
|
|
|
|
return convertDalToService(profile);
|
|
};
|
|
|
|
const listProfiles = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
projectId,
|
|
offset = 0,
|
|
limit = 20,
|
|
search,
|
|
enrollmentType,
|
|
caId,
|
|
includeMetrics = false,
|
|
expiringDays = 30
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
projectId: string;
|
|
offset?: number;
|
|
limit?: number;
|
|
search?: string;
|
|
enrollmentType?: EnrollmentType;
|
|
caId?: string;
|
|
includeMetrics?: boolean;
|
|
expiringDays?: number;
|
|
}): Promise<{
|
|
profiles: (TCertificateProfileWithConfigs & { metrics?: TCertificateProfileMetrics })[];
|
|
totalCount: number;
|
|
}> => {
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.CertificateManager
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionCertificateProfileActions.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const profiles = await certificateProfileDAL.findByProjectId(projectId, {
|
|
offset,
|
|
limit,
|
|
search,
|
|
enrollmentType,
|
|
caId,
|
|
includeMetrics,
|
|
expiringDays
|
|
});
|
|
|
|
const totalCount = await certificateProfileDAL.countByProjectId(projectId, {
|
|
search,
|
|
enrollmentType,
|
|
caId
|
|
});
|
|
|
|
const convertedProfiles = await Promise.all(
|
|
profiles.map(async (profile) => {
|
|
const profileWithConfigs = profile as TCertificateProfileWithConfigs;
|
|
|
|
let decryptedEstConfig = profileWithConfigs.estConfig;
|
|
if (decryptedEstConfig && profileWithConfigs.estConfigId) {
|
|
try {
|
|
const estConfig = await estEnrollmentConfigDAL.findById(profileWithConfigs.estConfigId);
|
|
if (estConfig && estConfig.encryptedCaChain) {
|
|
const decryptedCaChain = await decryptCaChain(
|
|
estConfig.encryptedCaChain,
|
|
projectId,
|
|
kmsService,
|
|
projectDAL
|
|
);
|
|
decryptedEstConfig = {
|
|
...decryptedEstConfig,
|
|
caChain: decryptedCaChain
|
|
};
|
|
} else if (decryptedEstConfig) {
|
|
decryptedEstConfig = {
|
|
...decryptedEstConfig,
|
|
caChain: ""
|
|
};
|
|
}
|
|
} catch (error) {
|
|
if (decryptedEstConfig) {
|
|
decryptedEstConfig = {
|
|
...decryptedEstConfig,
|
|
caChain: ""
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
const converted = convertDalToService(profileWithConfigs);
|
|
let result: TCertificateProfileWithConfigs & { metrics?: TCertificateProfileMetrics } = {
|
|
...converted,
|
|
estConfig: decryptedEstConfig,
|
|
apiConfig: profileWithConfigs.apiConfig
|
|
};
|
|
|
|
if (includeMetrics) {
|
|
const profileWithMetrics = profile as TCertificateProfileWithRawMetrics;
|
|
result = {
|
|
...result,
|
|
metrics: {
|
|
profileId: converted.id,
|
|
totalCertificates: parseInt(String(profileWithMetrics.total_certificates || 0), 10),
|
|
activeCertificates: parseInt(String(profileWithMetrics.active_certificates || 0), 10),
|
|
expiredCertificates: parseInt(String(profileWithMetrics.expired_certificates || 0), 10),
|
|
expiringCertificates: parseInt(String(profileWithMetrics.expiring_certificates || 0), 10),
|
|
revokedCertificates: parseInt(String(profileWithMetrics.revoked_certificates || 0), 10)
|
|
}
|
|
};
|
|
}
|
|
|
|
return result;
|
|
})
|
|
);
|
|
|
|
return {
|
|
profiles: convertedProfiles,
|
|
totalCount
|
|
};
|
|
};
|
|
|
|
const deleteProfile = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
profileId
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
profileId: string;
|
|
}): Promise<TCertificateProfile> => {
|
|
const profile = await certificateProfileDAL.findById(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.Delete,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const deletedProfile = await certificateProfileDAL.deleteById(profileId);
|
|
if (!deletedProfile) {
|
|
throw new NotFoundError({ message: "Failed to delete certificate profile" });
|
|
}
|
|
return convertDalToService(deletedProfile);
|
|
};
|
|
|
|
const getProfileCertificates = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
profileId,
|
|
offset = 0,
|
|
limit = 20,
|
|
status,
|
|
search
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
profileId: string;
|
|
offset?: number;
|
|
limit?: number;
|
|
status?: "active" | "expired" | "revoked";
|
|
search?: string;
|
|
}): Promise<TCertificateProfileCertificate[]> => {
|
|
const profile = await certificateProfileDAL.findById(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.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const certificates = await certificateProfileDAL.getCertificatesByProfile(profileId, {
|
|
offset,
|
|
limit,
|
|
status,
|
|
search
|
|
});
|
|
|
|
return certificates;
|
|
};
|
|
|
|
const getProfileMetrics = async ({
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
profileId,
|
|
expiringDays = 30
|
|
}: {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string;
|
|
profileId: string;
|
|
expiringDays?: number;
|
|
}): Promise<TCertificateProfileMetrics> => {
|
|
const profile = await certificateProfileDAL.findById(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.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
|
|
const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays);
|
|
return metrics;
|
|
};
|
|
|
|
const getEstConfigurationByProfile = async (
|
|
params:
|
|
| {
|
|
profileId: string;
|
|
isInternal: true;
|
|
}
|
|
| {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string | undefined;
|
|
profileId: string;
|
|
isInternal?: false;
|
|
}
|
|
) => {
|
|
const { profileId, isInternal = false } = params;
|
|
const profile = await certificateProfileDAL.findByIdWithConfigs(profileId);
|
|
if (!profile) {
|
|
throw new NotFoundError({ message: "Certificate profile not found" });
|
|
}
|
|
|
|
if (!isInternal) {
|
|
const { actor, actorId, actorAuthMethod, actorOrgId } = params as {
|
|
actor: ActorType;
|
|
actorId: string;
|
|
actorAuthMethod: ActorAuthMethod;
|
|
actorOrgId: string | undefined;
|
|
};
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: profile.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.CertificateManager
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionCertificateProfileActions.Read,
|
|
ProjectPermissionSub.CertificateProfiles
|
|
);
|
|
}
|
|
|
|
if (profile.enrollmentType !== EnrollmentType.EST) {
|
|
throw new ForbiddenRequestError({
|
|
message: "Profile is not configured for EST enrollment"
|
|
});
|
|
}
|
|
|
|
if (!profile.estConfig) {
|
|
throw new NotFoundError({ message: "EST configuration not found for this profile" });
|
|
}
|
|
|
|
return {
|
|
orgId: profile.projectId,
|
|
isEnabled: true,
|
|
caChain: profile.estConfig.caChain,
|
|
disableBootstrapCertValidation: profile.estConfig.disableBootstrapCaValidation,
|
|
hashedPassphrase: profile.estConfig.passphrase
|
|
};
|
|
};
|
|
|
|
return {
|
|
createProfile,
|
|
updateProfile,
|
|
getProfileById,
|
|
getProfileByIdWithConfigs,
|
|
getProfileBySlug,
|
|
listProfiles,
|
|
deleteProfile,
|
|
getProfileCertificates,
|
|
getProfileMetrics,
|
|
getEstConfigurationByProfile
|
|
};
|
|
};
|