mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 19:28:09 +00:00
Merge pull request #5011 from Infisical/PKI-37-add-acme-audit-logs
improvement(api): add acme audit logs
This commit is contained in:
@@ -49,6 +49,7 @@ import { TWebhookPayloads } from "@app/services/webhook/webhook-types";
|
|||||||
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
||||||
|
|
||||||
import { KmipPermission } from "../kmip/kmip-enum";
|
import { KmipPermission } from "../kmip/kmip-enum";
|
||||||
|
import { AcmeChallengeType, AcmeIdentifierType } from "../pki-acme/pki-acme-schemas";
|
||||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
@@ -78,7 +79,9 @@ export type TCreateAuditLogDTO = {
|
|||||||
| ScimClientActor
|
| ScimClientActor
|
||||||
| PlatformActor
|
| PlatformActor
|
||||||
| UnknownUserActor
|
| UnknownUserActor
|
||||||
| KmipClientActor;
|
| KmipClientActor
|
||||||
|
| AcmeProfileActor
|
||||||
|
| AcmeAccountActor;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
} & BaseAuthData;
|
} & BaseAuthData;
|
||||||
@@ -574,7 +577,18 @@ export enum EventType {
|
|||||||
APPROVAL_REQUEST_CANCEL = "approval-request-cancel",
|
APPROVAL_REQUEST_CANCEL = "approval-request-cancel",
|
||||||
APPROVAL_REQUEST_GRANT_LIST = "approval-request-grant-list",
|
APPROVAL_REQUEST_GRANT_LIST = "approval-request-grant-list",
|
||||||
APPROVAL_REQUEST_GRANT_GET = "approval-request-grant-get",
|
APPROVAL_REQUEST_GRANT_GET = "approval-request-grant-get",
|
||||||
APPROVAL_REQUEST_GRANT_REVOKE = "approval-request-grant-revoke"
|
APPROVAL_REQUEST_GRANT_REVOKE = "approval-request-grant-revoke",
|
||||||
|
|
||||||
|
// PKI ACME
|
||||||
|
CREATE_ACME_ACCOUNT = "create-acme-account",
|
||||||
|
RETRIEVE_ACME_ACCOUNT = "retrieve-acme-account",
|
||||||
|
CREATE_ACME_ORDER = "create-acme-order",
|
||||||
|
FINALIZE_ACME_ORDER = "finalize-acme-order",
|
||||||
|
DOWNLOAD_ACME_CERTIFICATE = "download-acme-certificate",
|
||||||
|
RESPOND_TO_ACME_CHALLENGE = "respond-to-acme-challenge",
|
||||||
|
PASS_ACME_CHALLENGE = "pass-acme-challenge",
|
||||||
|
ATTEMPT_ACME_CHALLENGE = "attempt-acme-challenge",
|
||||||
|
FAIL_ACME_CHALLENGE = "fail-acme-challenge"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterableSecretEvents: EventType[] = [
|
export const filterableSecretEvents: EventType[] = [
|
||||||
@@ -615,6 +629,15 @@ interface KmipClientActorMetadata {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AcmeProfileActorMetadata {
|
||||||
|
profileId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AcmeAccountActorMetadata {
|
||||||
|
profileId: string;
|
||||||
|
accountId: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface UnknownUserActorMetadata {}
|
interface UnknownUserActorMetadata {}
|
||||||
|
|
||||||
export interface UserActor {
|
export interface UserActor {
|
||||||
@@ -652,7 +675,25 @@ export interface ScimClientActor {
|
|||||||
metadata: ScimClientActorMetadata;
|
metadata: ScimClientActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor | PlatformActor | KmipClientActor;
|
export interface AcmeProfileActor {
|
||||||
|
type: ActorType.ACME_PROFILE;
|
||||||
|
metadata: AcmeProfileActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AcmeAccountActor {
|
||||||
|
type: ActorType.ACME_ACCOUNT;
|
||||||
|
metadata: AcmeAccountActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Actor =
|
||||||
|
| UserActor
|
||||||
|
| ServiceActor
|
||||||
|
| IdentityActor
|
||||||
|
| ScimClientActor
|
||||||
|
| PlatformActor
|
||||||
|
| KmipClientActor
|
||||||
|
| AcmeProfileActor
|
||||||
|
| AcmeAccountActor;
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
@@ -4368,6 +4409,84 @@ interface ApprovalRequestGrantRevokeEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateAcmeAccountEvent {
|
||||||
|
type: EventType.CREATE_ACME_ACCOUNT;
|
||||||
|
metadata: {
|
||||||
|
accountId: string;
|
||||||
|
publicKeyThumbprint: string;
|
||||||
|
emails?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RetrieveAcmeAccountEvent {
|
||||||
|
type: EventType.RETRIEVE_ACME_ACCOUNT;
|
||||||
|
metadata: {
|
||||||
|
accountId: string;
|
||||||
|
publicKeyThumbprint: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateAcmeOrderEvent {
|
||||||
|
type: EventType.CREATE_ACME_ORDER;
|
||||||
|
metadata: {
|
||||||
|
orderId: string;
|
||||||
|
identifiers: Array<{
|
||||||
|
type: AcmeIdentifierType;
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FinalizeAcmeOrderEvent {
|
||||||
|
type: EventType.FINALIZE_ACME_ORDER;
|
||||||
|
metadata: {
|
||||||
|
orderId: string;
|
||||||
|
csr: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadAcmeCertificateEvent {
|
||||||
|
type: EventType.DOWNLOAD_ACME_CERTIFICATE;
|
||||||
|
metadata: {
|
||||||
|
orderId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RespondToAcmeChallengeEvent {
|
||||||
|
type: EventType.RESPOND_TO_ACME_CHALLENGE;
|
||||||
|
metadata: {
|
||||||
|
challengeId: string;
|
||||||
|
type: AcmeChallengeType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface PassedAcmeChallengeEvent {
|
||||||
|
type: EventType.PASS_ACME_CHALLENGE;
|
||||||
|
metadata: {
|
||||||
|
challengeId: string;
|
||||||
|
type: AcmeChallengeType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttemptAcmeChallengeEvent {
|
||||||
|
type: EventType.ATTEMPT_ACME_CHALLENGE;
|
||||||
|
metadata: {
|
||||||
|
challengeId: string;
|
||||||
|
type: AcmeChallengeType;
|
||||||
|
retryCount: number;
|
||||||
|
errorMessage: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FailAcmeChallengeEvent {
|
||||||
|
type: EventType.FAIL_ACME_CHALLENGE;
|
||||||
|
metadata: {
|
||||||
|
challengeId: string;
|
||||||
|
type: AcmeChallengeType;
|
||||||
|
retryCount: number;
|
||||||
|
errorMessage: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| CreateSubOrganizationEvent
|
| CreateSubOrganizationEvent
|
||||||
| UpdateSubOrganizationEvent
|
| UpdateSubOrganizationEvent
|
||||||
@@ -4768,4 +4887,13 @@ export type Event =
|
|||||||
| ApprovalRequestCancelEvent
|
| ApprovalRequestCancelEvent
|
||||||
| ApprovalRequestGrantListEvent
|
| ApprovalRequestGrantListEvent
|
||||||
| ApprovalRequestGrantGetEvent
|
| ApprovalRequestGrantGetEvent
|
||||||
| ApprovalRequestGrantRevokeEvent;
|
| ApprovalRequestGrantRevokeEvent
|
||||||
|
| CreateAcmeAccountEvent
|
||||||
|
| RetrieveAcmeAccountEvent
|
||||||
|
| CreateAcmeOrderEvent
|
||||||
|
| FinalizeAcmeOrderEvent
|
||||||
|
| DownloadAcmeCertificateEvent
|
||||||
|
| RespondToAcmeChallengeEvent
|
||||||
|
| PassedAcmeChallengeEvent
|
||||||
|
| AttemptAcmeChallengeEvent
|
||||||
|
| FailAcmeChallengeEvent;
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ export const pkiAcmeChallengeDALFactory = (db: TDbClient) => {
|
|||||||
const result = await (tx || db)(TableName.PkiAcmeChallenge)
|
const result = await (tx || db)(TableName.PkiAcmeChallenge)
|
||||||
.join(TableName.PkiAcmeAuth, `${TableName.PkiAcmeChallenge}.authId`, `${TableName.PkiAcmeAuth}.id`)
|
.join(TableName.PkiAcmeAuth, `${TableName.PkiAcmeChallenge}.authId`, `${TableName.PkiAcmeAuth}.id`)
|
||||||
.join(TableName.PkiAcmeAccount, `${TableName.PkiAcmeAuth}.accountId`, `${TableName.PkiAcmeAccount}.id`)
|
.join(TableName.PkiAcmeAccount, `${TableName.PkiAcmeAuth}.accountId`, `${TableName.PkiAcmeAccount}.id`)
|
||||||
|
.join(
|
||||||
|
TableName.PkiCertificateProfile,
|
||||||
|
`${TableName.PkiAcmeAccount}.profileId`,
|
||||||
|
`${TableName.PkiCertificateProfile}.id`
|
||||||
|
)
|
||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.PkiAcmeChallenge),
|
selectAllTableCols(TableName.PkiAcmeChallenge),
|
||||||
db.ref("id").withSchema(TableName.PkiAcmeAuth).as("authId"),
|
db.ref("id").withSchema(TableName.PkiAcmeAuth).as("authId"),
|
||||||
@@ -131,7 +136,9 @@ export const pkiAcmeChallengeDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("identifierValue").withSchema(TableName.PkiAcmeAuth).as("authIdentifierValue"),
|
db.ref("identifierValue").withSchema(TableName.PkiAcmeAuth).as("authIdentifierValue"),
|
||||||
db.ref("expiresAt").withSchema(TableName.PkiAcmeAuth).as("authExpiresAt"),
|
db.ref("expiresAt").withSchema(TableName.PkiAcmeAuth).as("authExpiresAt"),
|
||||||
db.ref("id").withSchema(TableName.PkiAcmeAccount).as("accountId"),
|
db.ref("id").withSchema(TableName.PkiAcmeAccount).as("accountId"),
|
||||||
db.ref("publicKeyThumbprint").withSchema(TableName.PkiAcmeAccount).as("accountPublicKeyThumbprint")
|
db.ref("publicKeyThumbprint").withSchema(TableName.PkiAcmeAccount).as("accountPublicKeyThumbprint"),
|
||||||
|
db.ref("profileId").withSchema(TableName.PkiAcmeAccount).as("profileId"),
|
||||||
|
db.ref("projectId").withSchema(TableName.PkiCertificateProfile).as("projectId")
|
||||||
)
|
)
|
||||||
// For all challenges, acquire update lock on the auth to avoid race conditions
|
// For all challenges, acquire update lock on the auth to avoid race conditions
|
||||||
.forUpdate(TableName.PkiAcmeAuth)
|
.forUpdate(TableName.PkiAcmeAuth)
|
||||||
@@ -149,6 +156,8 @@ export const pkiAcmeChallengeDALFactory = (db: TDbClient) => {
|
|||||||
authExpiresAt,
|
authExpiresAt,
|
||||||
accountId,
|
accountId,
|
||||||
accountPublicKeyThumbprint,
|
accountPublicKeyThumbprint,
|
||||||
|
profileId,
|
||||||
|
projectId,
|
||||||
...challenge
|
...challenge
|
||||||
} = result;
|
} = result;
|
||||||
return {
|
return {
|
||||||
@@ -161,7 +170,11 @@ export const pkiAcmeChallengeDALFactory = (db: TDbClient) => {
|
|||||||
expiresAt: authExpiresAt,
|
expiresAt: authExpiresAt,
|
||||||
account: {
|
account: {
|
||||||
id: accountId,
|
id: accountId,
|
||||||
publicKeyThumbprint: accountPublicKeyThumbprint
|
publicKeyThumbprint: accountPublicKeyThumbprint,
|
||||||
|
project: {
|
||||||
|
id: projectId
|
||||||
|
},
|
||||||
|
profileId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { isPrivateIp } from "@app/lib/ip/ipRange";
|
import { isPrivateIp } from "@app/lib/ip/ipRange";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { EventType, TAuditLogServiceFactory } from "../audit-log/audit-log-types";
|
||||||
import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal";
|
import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal";
|
||||||
import {
|
import {
|
||||||
AcmeConnectionError,
|
AcmeConnectionError,
|
||||||
@@ -25,10 +27,12 @@ type TPkiAcmeChallengeServiceFactoryDep = {
|
|||||||
| "markAsInvalidCascadeById"
|
| "markAsInvalidCascadeById"
|
||||||
| "updateById"
|
| "updateById"
|
||||||
>;
|
>;
|
||||||
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pkiAcmeChallengeServiceFactory = ({
|
export const pkiAcmeChallengeServiceFactory = ({
|
||||||
acmeChallengeDAL
|
acmeChallengeDAL,
|
||||||
|
auditLogService
|
||||||
}: TPkiAcmeChallengeServiceFactoryDep): TPkiAcmeChallengeServiceFactory => {
|
}: TPkiAcmeChallengeServiceFactoryDep): TPkiAcmeChallengeServiceFactory => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const markChallengeAsReady = async (challengeId: string): Promise<TPkiAcmeChallenges> => {
|
const markChallengeAsReady = async (challengeId: string): Promise<TPkiAcmeChallenges> => {
|
||||||
@@ -113,7 +117,25 @@ export const pkiAcmeChallengeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
logger.info({ challengeId }, "ACME challenge response is correct, marking challenge as valid");
|
logger.info({ challengeId }, "ACME challenge response is correct, marking challenge as valid");
|
||||||
await acmeChallengeDAL.markAsValidCascadeById(challengeId);
|
await acmeChallengeDAL.markAsValidCascadeById(challengeId);
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: challenge.auth.account.project.id,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
profileId: challenge.auth.account.profileId,
|
||||||
|
accountId: challenge.auth.account.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.PASS_ACME_CHALLENGE,
|
||||||
|
metadata: {
|
||||||
|
challengeId,
|
||||||
|
type: challenge.type as AcmeChallengeType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (exp) {
|
} catch (exp) {
|
||||||
|
let finalAttempt = false;
|
||||||
if (retryCount >= 2) {
|
if (retryCount >= 2) {
|
||||||
logger.error(
|
logger.error(
|
||||||
exp,
|
exp,
|
||||||
@@ -121,35 +143,59 @@ export const pkiAcmeChallengeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
// This is the last attempt to validate the challenge response, if it fails, we mark the challenge as invalid
|
// This is the last attempt to validate the challenge response, if it fails, we mark the challenge as invalid
|
||||||
await acmeChallengeDAL.markAsInvalidCascadeById(challengeId);
|
await acmeChallengeDAL.markAsInvalidCascadeById(challengeId);
|
||||||
|
finalAttempt = true;
|
||||||
}
|
}
|
||||||
// Properly type and inspect the error
|
try {
|
||||||
if (axios.isAxiosError(exp)) {
|
// Properly type and inspect the error
|
||||||
const axiosError = exp as AxiosError;
|
if (axios.isAxiosError(exp)) {
|
||||||
const errorCode = axiosError.code;
|
const axiosError = exp as AxiosError;
|
||||||
const errorMessage = axiosError.message;
|
const errorCode = axiosError.code;
|
||||||
|
const errorMessage = axiosError.message;
|
||||||
|
|
||||||
if (errorCode === "ECONNREFUSED" || errorMessage.includes("ECONNREFUSED")) {
|
if (errorCode === "ECONNREFUSED" || errorMessage.includes("ECONNREFUSED")) {
|
||||||
throw new AcmeConnectionError({ message: "Connection refused" });
|
throw new AcmeConnectionError({ message: "Connection refused" });
|
||||||
|
}
|
||||||
|
if (errorCode === "ENOTFOUND" || errorMessage.includes("ENOTFOUND")) {
|
||||||
|
throw new AcmeDnsFailureError({ message: "Hostname could not be resolved (DNS failure)" });
|
||||||
|
}
|
||||||
|
if (errorCode === "ECONNRESET" || errorMessage.includes("ECONNRESET")) {
|
||||||
|
throw new AcmeConnectionError({ message: "Connection reset by peer" });
|
||||||
|
}
|
||||||
|
if (errorCode === "ECONNABORTED" || errorMessage.includes("timeout")) {
|
||||||
|
logger.error(exp, "Connection timed out while validating ACME challenge response");
|
||||||
|
throw new AcmeConnectionError({ message: "Connection timed out" });
|
||||||
|
}
|
||||||
|
logger.error(exp, "Unknown error validating ACME challenge response");
|
||||||
|
throw new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" });
|
||||||
}
|
}
|
||||||
if (errorCode === "ENOTFOUND" || errorMessage.includes("ENOTFOUND")) {
|
if (exp instanceof Error) {
|
||||||
throw new AcmeDnsFailureError({ message: "Hostname could not be resolved (DNS failure)" });
|
logger.error(exp, "Error validating ACME challenge response");
|
||||||
}
|
throw exp;
|
||||||
if (errorCode === "ECONNRESET" || errorMessage.includes("ECONNRESET")) {
|
|
||||||
throw new AcmeConnectionError({ message: "Connection reset by peer" });
|
|
||||||
}
|
|
||||||
if (errorCode === "ECONNABORTED" || errorMessage.includes("timeout")) {
|
|
||||||
logger.error(exp, "Connection timed out while validating ACME challenge response");
|
|
||||||
throw new AcmeConnectionError({ message: "Connection timed out" });
|
|
||||||
}
|
}
|
||||||
logger.error(exp, "Unknown error validating ACME challenge response");
|
logger.error(exp, "Unknown error validating ACME challenge response");
|
||||||
throw new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" });
|
throw new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" });
|
||||||
|
} catch (outterExp) {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: challenge.auth.account.project.id,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
profileId: challenge.auth.account.profileId,
|
||||||
|
accountId: challenge.auth.account.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: finalAttempt ? EventType.FAIL_ACME_CHALLENGE : EventType.ATTEMPT_ACME_CHALLENGE,
|
||||||
|
metadata: {
|
||||||
|
challengeId,
|
||||||
|
type: challenge.type as AcmeChallengeType,
|
||||||
|
retryCount,
|
||||||
|
errorMessage: exp instanceof Error ? exp.message : "Unknown error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
throw outterExp;
|
||||||
}
|
}
|
||||||
if (exp instanceof Error) {
|
|
||||||
logger.error(exp, "Error validating ACME challenge response");
|
|
||||||
throw exp;
|
|
||||||
}
|
|
||||||
logger.error(exp, "Unknown error validating ACME challenge response");
|
|
||||||
throw new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
|
import { EventType, TAuditLogServiceFactory } from "../audit-log/audit-log-types";
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal";
|
import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal";
|
||||||
import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal";
|
import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal";
|
||||||
@@ -136,6 +137,7 @@ type TPkiAcmeServiceFactoryDep = {
|
|||||||
certificateTemplateV2Service: Pick<TCertificateTemplateV2ServiceFactory, "validateCertificateRequest">;
|
certificateTemplateV2Service: Pick<TCertificateTemplateV2ServiceFactory, "validateCertificateRequest">;
|
||||||
acmeChallengeService: Pick<TPkiAcmeChallengeServiceFactory, "markChallengeAsReady">;
|
acmeChallengeService: Pick<TPkiAcmeChallengeServiceFactory, "markChallengeAsReady">;
|
||||||
pkiAcmeQueueService: Pick<TPkiAcmeQueueServiceFactory, "queueChallengeValidation">;
|
pkiAcmeQueueService: Pick<TPkiAcmeQueueServiceFactory, "queueChallengeValidation">;
|
||||||
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pkiAcmeServiceFactory = ({
|
export const pkiAcmeServiceFactory = ({
|
||||||
@@ -159,7 +161,8 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
certificateV3Service,
|
certificateV3Service,
|
||||||
certificateTemplateV2Service,
|
certificateTemplateV2Service,
|
||||||
acmeChallengeService,
|
acmeChallengeService,
|
||||||
pkiAcmeQueueService
|
pkiAcmeQueueService,
|
||||||
|
auditLogService
|
||||||
}: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => {
|
}: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => {
|
||||||
const validateAcmeProfile = async (profileId: string): Promise<TCertificateProfileWithConfigs> => {
|
const validateAcmeProfile = async (profileId: string): Promise<TCertificateProfileWithConfigs> => {
|
||||||
const profile = await certificateProfileDAL.findByIdWithConfigs(profileId);
|
const profile = await certificateProfileDAL.findByIdWithConfigs(profileId);
|
||||||
@@ -446,6 +449,23 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
throw new AcmeExternalAccountRequiredError({ message: "External account binding is required" });
|
throw new AcmeExternalAccountRequiredError({ message: "External account binding is required" });
|
||||||
}
|
}
|
||||||
if (existingAccount) {
|
if (existingAccount) {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_PROFILE,
|
||||||
|
metadata: {
|
||||||
|
profileId: profile.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.RETRIEVE_ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
accountId: existingAccount.id,
|
||||||
|
publicKeyThumbprint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
@@ -518,7 +538,25 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
publicKeyThumbprint,
|
publicKeyThumbprint,
|
||||||
emails: contact ?? []
|
emails: contact ?? []
|
||||||
});
|
});
|
||||||
// TODO: create audit log here
|
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_PROFILE,
|
||||||
|
metadata: {
|
||||||
|
profileId: profile.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
accountId: newAccount.id,
|
||||||
|
publicKeyThumbprint: newAccount.publicKeyThumbprint,
|
||||||
|
emails: newAccount.emails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 201,
|
status: 201,
|
||||||
body: {
|
body: {
|
||||||
@@ -647,7 +685,26 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
// TODO: create audit log here
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
profileId: account.profileId,
|
||||||
|
accountId: account.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_ACME_ORDER,
|
||||||
|
metadata: {
|
||||||
|
orderId: createdOrder.id,
|
||||||
|
identifiers: authorizations.map((auth) => ({
|
||||||
|
type: auth.identifierType as AcmeIdentifierType,
|
||||||
|
value: auth.identifierValue
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return { ...createdOrder, authorizations, account };
|
return { ...createdOrder, authorizations, account };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -875,6 +932,23 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
order = updatedOrder;
|
order = updatedOrder;
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
profileId,
|
||||||
|
accountId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.FINALIZE_ACME_ORDER,
|
||||||
|
metadata: {
|
||||||
|
orderId: updatedOrder.id,
|
||||||
|
csr: updatedOrder.csr!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if (order.status !== AcmeOrderStatus.Valid) {
|
} else if (order.status !== AcmeOrderStatus.Valid) {
|
||||||
throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" });
|
throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" });
|
||||||
}
|
}
|
||||||
@@ -930,6 +1004,24 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
|
|
||||||
const certLeaf = certObj.toString("pem").trim().replace("\n", "\r\n");
|
const certLeaf = certObj.toString("pem").trim().replace("\n", "\r\n");
|
||||||
const certChain = certificateChain.trim().replace("\n", "\r\n");
|
const certChain = certificateChain.trim().replace("\n", "\r\n");
|
||||||
|
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
profileId,
|
||||||
|
accountId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.DOWNLOAD_ACME_CERTIFICATE,
|
||||||
|
metadata: {
|
||||||
|
orderId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body:
|
body:
|
||||||
@@ -1012,6 +1104,7 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
authzId: string;
|
authzId: string;
|
||||||
challengeId: string;
|
challengeId: string;
|
||||||
}): Promise<TAcmeResponse<TRespondToAcmeChallengeResponse>> => {
|
}): Promise<TAcmeResponse<TRespondToAcmeChallengeResponse>> => {
|
||||||
|
const profile = await validateAcmeProfile(profileId);
|
||||||
const result = await acmeChallengeDAL.findByAccountAuthAndChallengeId(accountId, authzId, challengeId);
|
const result = await acmeChallengeDAL.findByAccountAuthAndChallengeId(accountId, authzId, challengeId);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new NotFoundError({ message: "ACME challenge not found" });
|
throw new NotFoundError({ message: "ACME challenge not found" });
|
||||||
@@ -1019,6 +1112,23 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
await acmeChallengeService.markChallengeAsReady(challengeId);
|
await acmeChallengeService.markChallengeAsReady(challengeId);
|
||||||
await pkiAcmeQueueService.queueChallengeValidation(challengeId);
|
await pkiAcmeQueueService.queueChallengeValidation(challengeId);
|
||||||
const challenge = (await acmeChallengeDAL.findByIdForChallengeValidation(challengeId))!;
|
const challenge = (await acmeChallengeDAL.findByIdForChallengeValidation(challengeId))!;
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
projectId: profile.projectId,
|
||||||
|
actor: {
|
||||||
|
type: ActorType.ACME_ACCOUNT,
|
||||||
|
metadata: {
|
||||||
|
profileId,
|
||||||
|
accountId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: EventType.RESPOND_TO_ACME_CHALLENGE,
|
||||||
|
metadata: {
|
||||||
|
challengeId,
|
||||||
|
type: challenge.type as AcmeChallengeType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
|
|||||||
@@ -2303,7 +2303,8 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const acmeChallengeService = pkiAcmeChallengeServiceFactory({
|
const acmeChallengeService = pkiAcmeChallengeServiceFactory({
|
||||||
acmeChallengeDAL
|
acmeChallengeDAL,
|
||||||
|
auditLogService
|
||||||
});
|
});
|
||||||
|
|
||||||
const pkiAcmeQueueService = await pkiAcmeQueueServiceFactory({
|
const pkiAcmeQueueService = await pkiAcmeQueueServiceFactory({
|
||||||
@@ -2332,7 +2333,8 @@ export const registerRoutes = async (
|
|||||||
certificateV3Service,
|
certificateV3Service,
|
||||||
certificateTemplateV2Service,
|
certificateTemplateV2Service,
|
||||||
acmeChallengeService,
|
acmeChallengeService,
|
||||||
pkiAcmeQueueService
|
pkiAcmeQueueService,
|
||||||
|
auditLogService
|
||||||
});
|
});
|
||||||
|
|
||||||
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
||||||
|
|||||||
@@ -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_PROFILE = "acmeProfile",
|
||||||
ACME_ACCOUNT = "acmeAccount",
|
ACME_ACCOUNT = "acmeAccount",
|
||||||
UNKNOWN_USER = "unknownUser"
|
UNKNOWN_USER = "unknownUser"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
AcmeAccountActor,
|
||||||
|
AcmeProfileActor,
|
||||||
IdentityActor,
|
IdentityActor,
|
||||||
KmipClientActor,
|
KmipClientActor,
|
||||||
PlatformActor,
|
PlatformActor,
|
||||||
@@ -60,6 +62,8 @@ export type TSecretModifiedEvent = {
|
|||||||
| ScimClientActor
|
| ScimClientActor
|
||||||
| PlatformActor
|
| PlatformActor
|
||||||
| UnknownUserActor
|
| UnknownUserActor
|
||||||
|
| AcmeAccountActor
|
||||||
|
| AcmeProfileActor
|
||||||
| KmipClientActor;
|
| KmipClientActor;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export enum ActorType {
|
|||||||
USER = "user",
|
USER = "user",
|
||||||
SERVICE = "service",
|
SERVICE = "service",
|
||||||
IDENTITY = "identity",
|
IDENTITY = "identity",
|
||||||
|
ACME_PROFILE = "acmeProfile",
|
||||||
|
ACME_ACCOUNT = "acmeAccount",
|
||||||
UNKNOWN_USER = "unknownUser"
|
UNKNOWN_USER = "unknownUser"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ interface KmipClientActorMetadata {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AcmeAccountActorMetadata {
|
||||||
|
profileId: string;
|
||||||
|
accountId: string;
|
||||||
|
}
|
||||||
|
interface AcmeProfileActorMetadata {
|
||||||
|
profileId: string;
|
||||||
|
}
|
||||||
interface UserActor {
|
interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
@@ -67,13 +74,25 @@ export interface UnknownUserActor {
|
|||||||
type: ActorType.UNKNOWN_USER;
|
type: ActorType.UNKNOWN_USER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AcmeProfileActor {
|
||||||
|
type: ActorType.ACME_PROFILE;
|
||||||
|
metadata: AcmeProfileActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AcmeAccountActor {
|
||||||
|
type: ActorType.ACME_ACCOUNT;
|
||||||
|
metadata: AcmeAccountActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
export type Actor =
|
export type Actor =
|
||||||
| UserActor
|
| UserActor
|
||||||
| ServiceActor
|
| ServiceActor
|
||||||
| IdentityActor
|
| IdentityActor
|
||||||
| PlatformActor
|
| PlatformActor
|
||||||
| UnknownUserActor
|
| UnknownUserActor
|
||||||
| KmipClientActor;
|
| KmipClientActor
|
||||||
|
| AcmeProfileActor
|
||||||
|
| AcmeAccountActor;
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ export const LogsTableRow = ({ auditLog, rowNumber, timezone }: Props) => {
|
|||||||
{auditLog.actor.type === ActorType.IDENTITY && (
|
{auditLog.actor.type === ActorType.IDENTITY && (
|
||||||
<Tag label="identity_name" value={auditLog.actor.metadata.name} />
|
<Tag label="identity_name" value={auditLog.actor.metadata.name} />
|
||||||
)}
|
)}
|
||||||
|
{auditLog.actor.type === ActorType.ACME_PROFILE && (
|
||||||
|
<Tag label="acme_profile_id" value={auditLog.actor.metadata.profileId} />
|
||||||
|
)}
|
||||||
|
{auditLog.actor.type === ActorType.ACME_ACCOUNT && (
|
||||||
|
<Tag label="acme_account_id" value={auditLog.actor.metadata.accountId} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user