mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 10:27:26 +00:00
issuing cert
This commit is contained in:
@@ -19,3 +19,4 @@ Feature: Challenge
|
|||||||
Then I select challenge with type http-01 for domain localhost from order at order as challenge
|
Then I select challenge with type http-01 for domain localhost from order at order as challenge
|
||||||
Then I serve challenge response for challenge at localhost
|
Then I serve challenge response for challenge at localhost
|
||||||
Then I tell ACME server that challenge is ready to be verified
|
Then I tell ACME server that challenge is ready to be verified
|
||||||
|
Then I poll and finalize the ACME order order
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
|
|
||||||
t.timestamp("expiresAt").notNullable();
|
t.timestamp("expiresAt").notNullable();
|
||||||
|
|
||||||
|
t.string("csr").nullable();
|
||||||
|
t.string("certificate").nullable();
|
||||||
|
t.string("certificateChain").nullable();
|
||||||
|
|
||||||
|
t.string("error").nullable();
|
||||||
|
|
||||||
// Order status
|
// Order status
|
||||||
t.string("status").notNullable(); // pending, ready, processing, valid, invalid
|
t.string("status").notNullable(); // pending, ready, processing, valid, invalid
|
||||||
|
|
||||||
@@ -160,6 +166,9 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
// Challenge status
|
// Challenge status
|
||||||
t.string("status").notNullable(); // pending, processing, valid, invalid
|
t.string("status").notNullable(); // pending, processing, valid, invalid
|
||||||
|
|
||||||
|
// Error message when the challenge fails
|
||||||
|
t.string("error").nullable();
|
||||||
|
|
||||||
// Validation timestamp
|
// Validation timestamp
|
||||||
t.timestamp("validatedAt").nullable();
|
t.timestamp("validatedAt").nullable();
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal";
|
|
||||||
import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal";
|
import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal";
|
||||||
import { AcmeConnectionError, AcmeDnsFailureError, AcmeIncorrectResponseError } from "./pki-acme-errors";
|
import { AcmeConnectionError, AcmeDnsFailureError, AcmeIncorrectResponseError } from "./pki-acme-errors";
|
||||||
import { AcmeAuthStatus, AcmeChallengeStatus, AcmeChallengeType } from "./pki-acme-schemas";
|
import { AcmeAuthStatus, AcmeChallengeStatus, AcmeChallengeType } from "./pki-acme-schemas";
|
||||||
import { TPkiAcmeChallengeServiceFactory } from "./pki-acme-types";
|
import { TPkiAcmeChallengeServiceFactory } from "./pki-acme-types";
|
||||||
|
|
||||||
type TPkiAcmeChallengeServiceFactoryDep = {
|
type TPkiAcmeChallengeServiceFactoryDep = {
|
||||||
acmeAuthDAL: Pick<TPkiAcmeAuthDALFactory, "updateById">;
|
|
||||||
acmeChallengeDAL: Pick<
|
acmeChallengeDAL: Pick<
|
||||||
TPkiAcmeChallengeDALFactory,
|
TPkiAcmeChallengeDALFactory,
|
||||||
"transaction" | "findByIdForChallengeValidation" | "markAsValidCascadeById" | "markAsInvalidCascadeById"
|
"transaction" | "findByIdForChallengeValidation" | "markAsValidCascadeById" | "markAsInvalidCascadeById"
|
||||||
@@ -16,7 +14,6 @@ type TPkiAcmeChallengeServiceFactoryDep = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const pkiAcmeChallengeServiceFactory = ({
|
export const pkiAcmeChallengeServiceFactory = ({
|
||||||
acmeAuthDAL,
|
|
||||||
acmeChallengeDAL
|
acmeChallengeDAL
|
||||||
}: TPkiAcmeChallengeServiceFactoryDep): TPkiAcmeChallengeServiceFactory => {
|
}: TPkiAcmeChallengeServiceFactoryDep): TPkiAcmeChallengeServiceFactory => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|||||||
@@ -507,3 +507,24 @@ export class AcmeDnsFailureError extends AcmeError {
|
|||||||
this.name = "AcmeDnsFailureError";
|
this.name = "AcmeDnsFailureError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AcmeOrderNotReadyError extends AcmeError {
|
||||||
|
constructor({
|
||||||
|
detail = "The order is not ready",
|
||||||
|
error,
|
||||||
|
message
|
||||||
|
}: {
|
||||||
|
detail?: string;
|
||||||
|
error?: unknown;
|
||||||
|
message?: string;
|
||||||
|
} = {}) {
|
||||||
|
super({
|
||||||
|
type: AcmeErrorType.OrderNotReady,
|
||||||
|
detail,
|
||||||
|
status: 403,
|
||||||
|
error,
|
||||||
|
message
|
||||||
|
});
|
||||||
|
this.name = "AcmeOrderNotReadyError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ export type TPkiAcmeOrderDALFactory = ReturnType<typeof pkiAcmeOrderDALFactory>;
|
|||||||
export const pkiAcmeOrderDALFactory = (db: TDbClient) => {
|
export const pkiAcmeOrderDALFactory = (db: TDbClient) => {
|
||||||
const pkiAcmeOrderOrm = ormify(db, TableName.PkiAcmeOrder);
|
const pkiAcmeOrderOrm = ormify(db, TableName.PkiAcmeOrder);
|
||||||
|
|
||||||
|
const findByIdForFinalization = async (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const order = await (tx || db)(TableName.PkiAcmeOrder).forUpdate().where({ id }).first();
|
||||||
|
return order || null;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find PKI ACME order by id for finalization" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const findByAccountAndOrderIdWithAuthorizations = async (accountId: string, orderId: string, tx?: Knex) => {
|
const findByAccountAndOrderIdWithAuthorizations = async (accountId: string, orderId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const rows = await (tx || db)(TableName.PkiAcmeOrder)
|
const rows = await (tx || db)(TableName.PkiAcmeOrder)
|
||||||
@@ -53,6 +62,7 @@ export const pkiAcmeOrderDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...pkiAcmeOrderOrm,
|
...pkiAcmeOrderOrm,
|
||||||
|
findByIdForFinalization,
|
||||||
findByAccountAndOrderIdWithAuthorizations
|
findByAccountAndOrderIdWithAuthorizations
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { NotFoundError } from "@app/lib/errors";
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
||||||
|
|
||||||
|
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
||||||
import {
|
import {
|
||||||
EnrollmentType,
|
EnrollmentType,
|
||||||
TCertificateProfileWithConfigs
|
TCertificateProfileWithConfigs
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
AcmeBadPublicKeyError,
|
AcmeBadPublicKeyError,
|
||||||
AcmeError,
|
AcmeError,
|
||||||
AcmeMalformedError,
|
AcmeMalformedError,
|
||||||
|
AcmeOrderNotReadyError,
|
||||||
AcmeServerInternalError,
|
AcmeServerInternalError,
|
||||||
AcmeUnauthorizedError,
|
AcmeUnauthorizedError,
|
||||||
AcmeUnsupportedIdentifierError
|
AcmeUnsupportedIdentifierError
|
||||||
@@ -64,11 +66,15 @@ import {
|
|||||||
|
|
||||||
type TPkiAcmeServiceFactoryDep = {
|
type TPkiAcmeServiceFactoryDep = {
|
||||||
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findById">;
|
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findById">;
|
||||||
|
internalCertificateAuthorityService: Pick<TInternalCertificateAuthorityServiceFactory, "signCertFromCa">;
|
||||||
acmeAccountDAL: Pick<
|
acmeAccountDAL: Pick<
|
||||||
TPkiAcmeAccountDALFactory,
|
TPkiAcmeAccountDALFactory,
|
||||||
"findByProjectIdAndAccountId" | "findByProfileIdAndPublicKeyThumbprintAndAlg" | "create"
|
"findByProjectIdAndAccountId" | "findByProfileIdAndPublicKeyThumbprintAndAlg" | "create"
|
||||||
>;
|
>;
|
||||||
acmeOrderDAL: Pick<TPkiAcmeOrderDALFactory, "create" | "transaction" | "findByAccountAndOrderIdWithAuthorizations">;
|
acmeOrderDAL: Pick<
|
||||||
|
TPkiAcmeOrderDALFactory,
|
||||||
|
"create" | "transaction" | "updateById" | "findByAccountAndOrderIdWithAuthorizations" | "findByIdForFinalization"
|
||||||
|
>;
|
||||||
acmeAuthDAL: Pick<TPkiAcmeAuthDALFactory, "create" | "findByAccountIdAndAuthIdWithChallenges">;
|
acmeAuthDAL: Pick<TPkiAcmeAuthDALFactory, "create" | "findByAccountIdAndAuthIdWithChallenges">;
|
||||||
acmeOrderAuthDAL: Pick<TPkiAcmeOrderAuthDALFactory, "insertMany">;
|
acmeOrderAuthDAL: Pick<TPkiAcmeOrderAuthDALFactory, "insertMany">;
|
||||||
acmeChallengeDAL: Pick<
|
acmeChallengeDAL: Pick<
|
||||||
@@ -80,6 +86,7 @@ type TPkiAcmeServiceFactoryDep = {
|
|||||||
|
|
||||||
export const pkiAcmeServiceFactory = ({
|
export const pkiAcmeServiceFactory = ({
|
||||||
certificateProfileDAL,
|
certificateProfileDAL,
|
||||||
|
internalCertificateAuthorityService,
|
||||||
acmeAccountDAL,
|
acmeAccountDAL,
|
||||||
acmeOrderDAL,
|
acmeOrderDAL,
|
||||||
acmeAuthDAL,
|
acmeAuthDAL,
|
||||||
@@ -497,8 +504,39 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
if (!order) {
|
if (!order) {
|
||||||
throw new NotFoundError({ message: "ACME order not found" });
|
throw new NotFoundError({ message: "ACME order not found" });
|
||||||
}
|
}
|
||||||
const { csr } = payload;
|
if (order.status === AcmeOrderStatus.Ready) {
|
||||||
// FIXME: Implement ACME finalize order
|
await acmeOrderDAL.transaction(async (tx) => {
|
||||||
|
const order = (await acmeOrderDAL.findByIdForFinalization(orderId, tx))!;
|
||||||
|
const profile = (await certificateProfileDAL.findById(profileId, tx))!;
|
||||||
|
if (order.status !== AcmeOrderStatus.Ready) {
|
||||||
|
throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" });
|
||||||
|
}
|
||||||
|
if (order.expiresAt < new Date()) {
|
||||||
|
throw new AcmeOrderNotReadyError({ message: "ACME order has expired" });
|
||||||
|
}
|
||||||
|
const { csr } = payload;
|
||||||
|
// TODO: validate the CSR and return badCSR error if it's invalid
|
||||||
|
const { certificate, certificateChain } = await internalCertificateAuthorityService.signCertFromCa({
|
||||||
|
isInternal: true,
|
||||||
|
certificateTemplateId: profile.certificateTemplateId,
|
||||||
|
csr,
|
||||||
|
notBefore: order.notBefore?.toISOString(),
|
||||||
|
notAfter: order.notAfter?.toISOString()
|
||||||
|
});
|
||||||
|
await acmeOrderDAL.updateById(
|
||||||
|
orderId,
|
||||||
|
{
|
||||||
|
status: AcmeOrderStatus.Valid,
|
||||||
|
csr,
|
||||||
|
certificate,
|
||||||
|
certificateChain
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (order.status !== AcmeOrderStatus.Valid) {
|
||||||
|
throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" });
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: buildAcmeOrderResource({ profileId, order }),
|
body: buildAcmeOrderResource({ profileId, order }),
|
||||||
|
|||||||
Reference in New Issue
Block a user