mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
Issue cert
This commit is contained in:
@@ -63,10 +63,12 @@ import {
|
|||||||
TRawJwsPayload,
|
TRawJwsPayload,
|
||||||
TRespondToAcmeChallengeResponse
|
TRespondToAcmeChallengeResponse
|
||||||
} from "./pki-acme-types";
|
} from "./pki-acme-types";
|
||||||
|
import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service";
|
||||||
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
type TPkiAcmeServiceFactoryDep = {
|
type TPkiAcmeServiceFactoryDep = {
|
||||||
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findById">;
|
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findByIdWithOwnerOrgId">;
|
||||||
internalCertificateAuthorityService: Pick<TInternalCertificateAuthorityServiceFactory, "signCertFromCa">;
|
certificateV3Service: Pick<TCertificateV3ServiceFactory, "signCertificateFromProfile">;
|
||||||
acmeAccountDAL: Pick<
|
acmeAccountDAL: Pick<
|
||||||
TPkiAcmeAccountDALFactory,
|
TPkiAcmeAccountDALFactory,
|
||||||
"findByProjectIdAndAccountId" | "findByProfileIdAndPublicKeyThumbprintAndAlg" | "create"
|
"findByProjectIdAndAccountId" | "findByProfileIdAndPublicKeyThumbprintAndAlg" | "create"
|
||||||
@@ -86,7 +88,7 @@ type TPkiAcmeServiceFactoryDep = {
|
|||||||
|
|
||||||
export const pkiAcmeServiceFactory = ({
|
export const pkiAcmeServiceFactory = ({
|
||||||
certificateProfileDAL,
|
certificateProfileDAL,
|
||||||
internalCertificateAuthorityService,
|
certificateV3Service,
|
||||||
acmeAccountDAL,
|
acmeAccountDAL,
|
||||||
acmeOrderDAL,
|
acmeOrderDAL,
|
||||||
acmeAuthDAL,
|
acmeAuthDAL,
|
||||||
@@ -507,7 +509,8 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
if (order.status === AcmeOrderStatus.Ready) {
|
if (order.status === AcmeOrderStatus.Ready) {
|
||||||
order = await acmeOrderDAL.transaction(async (tx) => {
|
order = await acmeOrderDAL.transaction(async (tx) => {
|
||||||
const order = (await acmeOrderDAL.findByIdForFinalization(orderId, tx))!;
|
const order = (await acmeOrderDAL.findByIdForFinalization(orderId, tx))!;
|
||||||
const profile = (await certificateProfileDAL.findById(profileId, tx))!;
|
// TODO: ideally, this should be doen with onRequest: verifyAuth([AuthMode.ACME_JWS_SIGNATURE]), instead
|
||||||
|
const { ownerOrgId: actorOrgId } = (await certificateProfileDAL.findByIdWithOwnerOrgId(profileId, tx))!;
|
||||||
if (order.status !== AcmeOrderStatus.Ready) {
|
if (order.status !== AcmeOrderStatus.Ready) {
|
||||||
throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" });
|
throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" });
|
||||||
}
|
}
|
||||||
@@ -516,20 +519,30 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
const { csr } = payload;
|
const { csr } = payload;
|
||||||
// TODO: validate the CSR and return badCSR error if it's invalid
|
// TODO: validate the CSR and return badCSR error if it's invalid
|
||||||
const { certificate, certificateChain } = await internalCertificateAuthorityService.signCertFromCa({
|
// TODO: this should be the same transaction?
|
||||||
isInternal: true,
|
const { certificate, certificateChain, certificateId } = await certificateV3Service.signCertificateFromProfile({
|
||||||
certificateTemplateId: profile.certificateTemplateId,
|
actor: ActorType.ACME_ACCOUNT,
|
||||||
|
actorId: accountId,
|
||||||
|
actorAuthMethod: null,
|
||||||
|
actorOrgId,
|
||||||
|
profileId,
|
||||||
csr,
|
csr,
|
||||||
notBefore: order.notBefore?.toISOString(),
|
notBefore: order.notBefore ? new Date(order.notBefore) : undefined,
|
||||||
notAfter: order.notAfter?.toISOString()
|
notAfter: order.notAfter ? new Date(order.notAfter) : undefined,
|
||||||
|
validity: {
|
||||||
|
// TODO: read config from the profile to get the expiration time instead
|
||||||
|
ttl: (24 * 60 * 60 * 1000).toString()
|
||||||
|
},
|
||||||
|
enrollmentType: EnrollmentType.ACME
|
||||||
});
|
});
|
||||||
|
// TODO: associate the certificate with the order
|
||||||
await acmeOrderDAL.updateById(
|
await acmeOrderDAL.updateById(
|
||||||
orderId,
|
orderId,
|
||||||
{
|
{
|
||||||
status: AcmeOrderStatus.Valid,
|
status: AcmeOrderStatus.Valid,
|
||||||
csr,
|
csr,
|
||||||
certificateChain,
|
certificateChain,
|
||||||
certificate: certificate.toString("pem")
|
certificate
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1175,20 +1175,6 @@ export const registerRoutes = async (
|
|||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const acmeChallengeService = pkiAcmeChallengeServiceFactory({
|
|
||||||
acmeAuthDAL,
|
|
||||||
acmeChallengeDAL
|
|
||||||
});
|
|
||||||
const pkiAcmeService = pkiAcmeServiceFactory({
|
|
||||||
certificateProfileDAL,
|
|
||||||
acmeAccountDAL,
|
|
||||||
acmeOrderDAL,
|
|
||||||
acmeAuthDAL,
|
|
||||||
acmeOrderAuthDAL,
|
|
||||||
acmeChallengeDAL,
|
|
||||||
acmeChallengeService
|
|
||||||
});
|
|
||||||
|
|
||||||
const pkiAlertService = pkiAlertServiceFactory({
|
const pkiAlertService = pkiAlertServiceFactory({
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
@@ -2209,6 +2195,20 @@ export const registerRoutes = async (
|
|||||||
estEnrollmentConfigDAL
|
estEnrollmentConfigDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const acmeChallengeService = pkiAcmeChallengeServiceFactory({
|
||||||
|
acmeChallengeDAL
|
||||||
|
});
|
||||||
|
const pkiAcmeService = pkiAcmeServiceFactory({
|
||||||
|
certificateV3Service,
|
||||||
|
certificateProfileDAL,
|
||||||
|
acmeAccountDAL,
|
||||||
|
acmeOrderDAL,
|
||||||
|
acmeAuthDAL,
|
||||||
|
acmeOrderAuthDAL,
|
||||||
|
acmeChallengeDAL,
|
||||||
|
acmeChallengeService
|
||||||
|
});
|
||||||
|
|
||||||
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
||||||
pkiSubscriberDAL,
|
pkiSubscriberDAL,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ import { ms } from "@app/lib/ms";
|
|||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { 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 {
|
|
||||||
ACMESANType,
|
|
||||||
CertificateOrderStatus,
|
|
||||||
CertKeyAlgorithm,
|
|
||||||
CertSignatureAlgorithm
|
|
||||||
} from "@app/services/certificate/certificate-types";
|
|
||||||
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
|
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
import {
|
import {
|
||||||
CertExtendedKeyUsageType,
|
CertExtendedKeyUsageType,
|
||||||
@@ -20,7 +14,14 @@ import {
|
|||||||
} from "@app/services/certificate-common/certificate-constants";
|
} from "@app/services/certificate-common/certificate-constants";
|
||||||
import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils";
|
import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils";
|
||||||
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
|
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
|
||||||
|
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
import {
|
||||||
|
ACMESANType,
|
||||||
|
CertificateOrderStatus,
|
||||||
|
CertKeyAlgorithm,
|
||||||
|
CertSignatureAlgorithm
|
||||||
|
} from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
interface CertificateRequestForService {
|
interface CertificateRequestForService {
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
@@ -204,7 +205,8 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
ttl: req.body.ttl
|
ttl: req.body.ttl
|
||||||
},
|
},
|
||||||
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
||||||
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined
|
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
|
||||||
|
enrollmentType: EnrollmentType.API
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export enum ActorType { // would extend to AWS, Azure, ...
|
|||||||
IDENTITY = "identity",
|
IDENTITY = "identity",
|
||||||
Machine = "machine",
|
Machine = "machine",
|
||||||
SCIM_CLIENT = "scimClient",
|
SCIM_CLIENT = "scimClient",
|
||||||
|
ACME_ACCOUNT = "acmeAccount",
|
||||||
UNKNOWN_USER = "unknownUser"
|
UNKNOWN_USER = "unknownUser"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,23 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findByIdWithOwnerOrgId = async (
|
||||||
|
id: string,
|
||||||
|
tx?: Knex
|
||||||
|
): Promise<(TCertificateProfile & { ownerOrgId: string }) | undefined> => {
|
||||||
|
try {
|
||||||
|
const certificateProfile = (await (tx || db)(TableName.PkiCertificateProfile)
|
||||||
|
.join(TableName.Project, `${TableName.PkiCertificateProfile}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.select(selectAllTableCols(TableName.PkiCertificateProfile))
|
||||||
|
.select(db.ref("orgId").withSchema(TableName.Project).as("ownerOrgId"))
|
||||||
|
.where({ id })
|
||||||
|
.first()) as (TCertificateProfile & { ownerOrgId: string }) | undefined;
|
||||||
|
return certificateProfile;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find certificate profile by id with owner org id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const findByIdWithConfigs = async (id: string, tx?: Knex): Promise<TCertificateProfileWithConfigs | undefined> => {
|
const findByIdWithConfigs = async (id: string, tx?: Knex): Promise<TCertificateProfileWithConfigs | undefined> => {
|
||||||
try {
|
try {
|
||||||
const query = (tx || db)(TableName.PkiCertificateProfile)
|
const query = (tx || db)(TableName.PkiCertificateProfile)
|
||||||
@@ -444,6 +461,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
updateById,
|
updateById,
|
||||||
deleteById,
|
deleteById,
|
||||||
findById,
|
findById,
|
||||||
|
findByIdWithOwnerOrgId,
|
||||||
findByIdWithConfigs,
|
findByIdWithConfigs,
|
||||||
findBySlugAndProjectId,
|
findBySlugAndProjectId,
|
||||||
findByProjectId,
|
findByProjectId,
|
||||||
|
|||||||
@@ -697,6 +697,7 @@ describe("CertificateV3Service", () => {
|
|||||||
profileId,
|
profileId,
|
||||||
csr: mockCSR,
|
csr: mockCSR,
|
||||||
validity: mockValidity,
|
validity: mockValidity,
|
||||||
|
enrollmentType: EnrollmentType.API,
|
||||||
...mockActor
|
...mockActor
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -731,6 +732,7 @@ describe("CertificateV3Service", () => {
|
|||||||
profileId,
|
profileId,
|
||||||
csr: mockCSR,
|
csr: mockCSR,
|
||||||
validity: mockValidity,
|
validity: mockValidity,
|
||||||
|
enrollmentType: EnrollmentType.API,
|
||||||
...mockActor
|
...mockActor
|
||||||
})
|
})
|
||||||
).rejects.toThrow(ForbiddenRequestError);
|
).rejects.toThrow(ForbiddenRequestError);
|
||||||
@@ -740,6 +742,7 @@ describe("CertificateV3Service", () => {
|
|||||||
profileId,
|
profileId,
|
||||||
csr: mockCSR,
|
csr: mockCSR,
|
||||||
validity: mockValidity,
|
validity: mockValidity,
|
||||||
|
enrollmentType: EnrollmentType.API,
|
||||||
...mockActor
|
...mockActor
|
||||||
})
|
})
|
||||||
).rejects.toThrow("Profile is not configured for api enrollment");
|
).rejects.toThrow("Profile is not configured for api enrollment");
|
||||||
|
|||||||
@@ -107,6 +107,13 @@ const validateProfileAndPermissions = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: NOT SURE IF THIS IS SECURE TO BY PASS THE PERMISSION CHECK FOR ACME ACCOUNTS
|
||||||
|
// may need to consider this carefully
|
||||||
|
// TODO: check actor/profile ownership as well
|
||||||
|
if (actor === ActorType.ACME_ACCOUNT && requiredEnrollmentType === EnrollmentType.ACME) {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -484,7 +491,8 @@ export const certificateV3ServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
enrollmentType
|
||||||
}: TSignCertificateFromProfileDTO): Promise<Omit<TCertificateFromProfileResponse, "privateKey">> => {
|
}: TSignCertificateFromProfileDTO): Promise<Omit<TCertificateFromProfileResponse, "privateKey">> => {
|
||||||
const profile = await validateProfileAndPermissions(
|
const profile = await validateProfileAndPermissions(
|
||||||
profileId,
|
profileId,
|
||||||
@@ -494,7 +502,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
certificateProfileDAL,
|
certificateProfileDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
EnrollmentType.API
|
enrollmentType
|
||||||
);
|
);
|
||||||
|
|
||||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types";
|
|
||||||
import {
|
import {
|
||||||
CertExtendedKeyUsageType,
|
CertExtendedKeyUsageType,
|
||||||
CertKeyUsageType,
|
CertKeyUsageType,
|
||||||
CertSubjectAlternativeNameType
|
CertSubjectAlternativeNameType
|
||||||
} from "../certificate-common/certificate-constants";
|
} from "../certificate-common/certificate-constants";
|
||||||
|
import { EnrollmentType } from "../certificate-profile/certificate-profile-types";
|
||||||
|
import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types";
|
||||||
|
|
||||||
export type TIssueCertificateFromProfileDTO = {
|
export type TIssueCertificateFromProfileDTO = {
|
||||||
profileId: string;
|
profileId: string;
|
||||||
@@ -35,6 +36,7 @@ export type TSignCertificateFromProfileDTO = {
|
|||||||
};
|
};
|
||||||
notBefore?: Date;
|
notBefore?: Date;
|
||||||
notAfter?: Date;
|
notAfter?: Date;
|
||||||
|
enrollmentType: EnrollmentType;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TOrderCertificateFromProfileDTO = {
|
export type TOrderCertificateFromProfileDTO = {
|
||||||
|
|||||||
Reference in New Issue
Block a user