diff --git a/.infisicalignore b/.infisicalignore index b935763c8c..ec1cbfe167 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -52,3 +52,6 @@ docs/integrations/app-connections/railway.mdx:generic-api-key:156 .github/workflows/validate-db-schemas.yml:generic-api-key:21 k8-operator/config/samples/universalAuthIdentitySecret.yaml:generic-api-key:8 docs/integrations/app-connections/redis.mdx:generic-api-key:80 +backend/src/ee/services/app-connections/chef/chef-connection-fns.ts:private-key:42 +docs/documentation/platform/pki/enrollment-methods/api.mdx:generic-api-key:93 +docs/documentation/platform/pki/enrollment-methods/api.mdx:private-key:139 \ No newline at end of file diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 30bdc71123..d511187d07 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -32,6 +32,7 @@ import { TPamResourceServiceFactory } from "@app/ee/services/pam-resource/pam-re import { TPamSessionServiceFactory } from "@app/ee/services/pam-session/pam-session-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPitServiceFactory } from "@app/ee/services/pit/pit-service"; +import { TPkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-types"; import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types"; import { RateLimitConfiguration, TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-types"; import { TRelayServiceFactory } from "@app/ee/services/relay/relay-service"; @@ -106,7 +107,6 @@ import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-c import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service"; import { TPkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { TPkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; -import { TPkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-types"; import { TProjectServiceFactory } from "@app/services/project/project-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service"; diff --git a/backend/src/db/migrations/20251104234547_add-pki-acme.ts b/backend/src/db/migrations/20251104234547_add-pki-acme.ts index c6b95009bd..9dff45ccbe 100644 --- a/backend/src/db/migrations/20251104234547_add-pki-acme.ts +++ b/backend/src/db/migrations/20251104234547_add-pki-acme.ts @@ -1,7 +1,9 @@ import { Knex } from "knex"; + +import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; + import { TableName } from "../schemas"; import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; -import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; // Notice: the old constraint name is "enrollmentType_check" instead of "enrollment_type_check" // with psql, if there's no quote around an identifier, it will be lowercased. diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index e104a83550..ce05ea3b67 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -1,6 +1,6 @@ import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-template-router"; - import { getConfig } from "@app/lib/config/env"; + import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router"; import { registerAccessApprovalRequestRouter } from "./access-approval-request-router"; import { registerAssumePrivilegeRouter } from "./assume-privilege-router"; diff --git a/backend/src/ee/routes/v1/pki-acme-router.ts b/backend/src/ee/routes/v1/pki-acme-router.ts index b1419c0866..627537c440 100644 --- a/backend/src/ee/routes/v1/pki-acme-router.ts +++ b/backend/src/ee/routes/v1/pki-acme-router.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -import type { TAcmeResponse, TAuthenciatedJwsPayload, TRawJwsPayload } from "@app/ee/services/pki-acme/pki-acme-types"; import { FastifyReply, FastifyRequest } from "fastify"; import { z } from "zod"; @@ -19,6 +18,7 @@ import { RespondToAcmeChallengeBodySchema, RespondToAcmeChallengeResponseSchema } from "@app/ee/services/pki-acme/pki-acme-schemas"; +import type { TAcmeResponse, TAuthenciatedJwsPayload, TRawJwsPayload } from "@app/ee/services/pki-acme/pki-acme-types"; import { ApiDocsTags } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index 78d9aeddc6..bd5bacc5fc 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -315,6 +315,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv .extend({ status: z.string(), comment: z.string().optional(), + createdAt: z.date(), isOrgMembershipActive: z.boolean().nullable().optional() }) .array(), diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index 16c7e962c7..abb94ec1ca 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -1,14 +1,14 @@ import { ForbiddenError } from "@casl/ability"; -import { AccessScope, ActionProjectType } from "@app/db/schemas"; +import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { TAdditionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal"; -import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; +import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal"; @@ -38,13 +38,13 @@ type TAccessApprovalPolicyServiceFactoryDep = { projectEnvDAL: Pick; accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory; accessApprovalPolicyBypasserDAL: TAccessApprovalPolicyBypasserDALFactory; + projectMembershipDAL: Pick; groupDAL: TGroupDALFactory; userDAL: Pick; accessApprovalRequestDAL: Pick; additionalPrivilegeDAL: Pick; accessApprovalRequestReviewerDAL: Pick; accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory; - membershipUserDAL: TMembershipUserDALFactory; }; export const accessApprovalPolicyServiceFactory = ({ @@ -60,7 +60,7 @@ export const accessApprovalPolicyServiceFactory = ({ accessApprovalRequestDAL, additionalPrivilegeDAL, accessApprovalRequestReviewerDAL, - membershipUserDAL + projectMembershipDAL }: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => { const $policyExists = async ({ envId, @@ -83,6 +83,21 @@ export const accessApprovalPolicyServiceFactory = ({ return policyId ? policy && policy.id !== policyId : Boolean(policy); }; + const verifyProjectUserMembership = async (userIds: string[], orgId: string, projectId: string) => { + if (userIds.length === 0) return; + const projectMemberships = (await projectMembershipDAL.findProjectMembershipsByUserIds(orgId, userIds)).filter( + (v) => v.projectId === projectId + ); + + if (projectMemberships.length !== userIds.length) { + const projectMemberUserIds = new Set(projectMemberships.map((member) => member.userId)); + const userIdsNotInProject = userIds.filter((id) => !projectMemberUserIds.has(id)); + throw new BadRequestError({ + message: `Some users are not members of the project: ${userIdsNotInProject.join(", ")}` + }); + } + }; + const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({ name, actor, @@ -160,7 +175,7 @@ export const accessApprovalPolicyServiceFactory = ({ if (invalidUsernames.length) { throw new BadRequestError({ - message: `Invalid approver user: ${invalidUsernames.join(", ")}` + message: `Invalid approver user: ${invalidUsernames.map((i) => i.username).join(", ")}` }); } @@ -171,6 +186,14 @@ export const accessApprovalPolicyServiceFactory = ({ })) ); } + + if (approverUserIds.length > 0) { + await verifyProjectUserMembership( + approverUserIds.map((au) => au.id), + project.orgId, + project.id + ); + } let groupBypassers: string[] = []; let bypasserUserIds: string[] = []; @@ -257,6 +280,8 @@ export const accessApprovalPolicyServiceFactory = ({ } if (bypasserUserIds.length) { + await verifyProjectUserMembership(bypasserUserIds, project.orgId, project.id); + await accessApprovalPolicyBypasserDAL.insertMany( bypasserUserIds.map((userId) => ({ bypasserUserId: userId, @@ -420,23 +445,6 @@ export const accessApprovalPolicyServiceFactory = ({ bypasserUserIds = [...new Set(bypasserUserIds.concat(bypasserUsers.map((user) => user.id)))]; } - // Validate user bypassers - if (bypasserUserIds.length > 0) { - const orgMemberships = await membershipUserDAL.find({ - $in: { actorUserId: bypasserUserIds }, - scopeOrgId: actorOrgId, - scope: AccessScope.Organization - }); - - if (orgMemberships.length !== bypasserUserIds.length) { - const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.actorUserId as string)); - const missingUserIds = bypasserUserIds.filter((id) => !foundUserIdsInOrg.has(id)); - throw new BadRequestError({ - message: `One or more specified bypasser users are not part of the organization or do not exist. Invalid or non-member user IDs: ${missingUserIds.join(", ")}` - }); - } - } - // Validate group bypassers if (groupBypassers.length > 0) { const orgGroups = await groupDAL.find({ @@ -487,7 +495,7 @@ export const accessApprovalPolicyServiceFactory = ({ if (invalidUsernames.length) { throw new BadRequestError({ - message: `Invalid approver user: ${invalidUsernames.join(", ")}` + message: `Invalid approver user: ${invalidUsernames.map((i) => i.username).join(", ")}` }); } @@ -498,6 +506,14 @@ export const accessApprovalPolicyServiceFactory = ({ })) ); } + + if (approverUserIds.length > 0) { + await verifyProjectUserMembership( + approverUserIds.map((au) => au.id), + actorOrgId, + accessApprovalPolicy.projectId + ); + } await accessApprovalPolicyApproverDAL.insertMany( approverUserIds.map((el) => ({ approverUserId: el.id, @@ -536,6 +552,8 @@ export const accessApprovalPolicyServiceFactory = ({ await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx); if (bypasserUserIds.length) { + await verifyProjectUserMembership(bypasserUserIds, actorOrgId, accessApprovalPolicy.projectId); + await accessApprovalPolicyBypasserDAL.insertMany( bypasserUserIds.map((userId) => ({ bypasserUserId: userId, diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 0b45544e3f..bcc2a07703 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -371,6 +371,7 @@ export enum EventType { SIGN_CERTIFICATE_FROM_PROFILE = "sign-certificate-from-profile", ORDER_CERTIFICATE_FROM_PROFILE = "order-certificate-from-profile", RENEW_CERTIFICATE = "renew-certificate", + GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE = "get-certificate-profile-latest-active-bundle", UPDATE_CERTIFICATE_RENEWAL_CONFIG = "update-certificate-renewal-config", DISABLE_CERTIFICATE_RENEWAL_CONFIG = "disable-certificate-renewal-config", ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration", @@ -2752,6 +2753,17 @@ interface OrderCertificateFromProfile { }; } +interface GetCertificateProfileLatestActiveBundle { + type: EventType.GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE; + metadata: { + certificateProfileId: string; + certificateId: string; + commonName: string; + profileName: string; + serialNumber: string; + }; +} + interface RenewCertificate { type: EventType.RENEW_CERTIFICATE; metadata: { @@ -4282,6 +4294,7 @@ export type Event = | DeleteCertificateProfile | GetCertificateProfile | ListCertificateProfiles + | GetCertificateProfileLatestActiveBundle | IssueCertificateFromProfile | SignCertificateFromProfile | OrderCertificateFromProfile diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts index 948f4af6e8..74cbd14660 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts @@ -1,8 +1,10 @@ +import { Knex } from "knex"; + import { TDbClient } from "@app/db"; import { TableName, TPkiAcmeChallenges } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; -import { Knex } from "knex"; + import { AcmeAuthStatus, AcmeChallengeStatus, AcmeOrderStatus } from "./pki-acme-schemas"; export type TPkiAcmeChallengeDALFactory = ReturnType; diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts index a058241c44..61bd0c1108 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts @@ -2,6 +2,7 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { isPrivateIp } from "@app/lib/ip/ipRange"; import { logger } from "@app/lib/logger"; + import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal"; import { AcmeConnectionError, diff --git a/backend/src/ee/services/pki-acme/pki-acme-fns.ts b/backend/src/ee/services/pki-acme/pki-acme-fns.ts index 0763a8ed06..828e0801ba 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-fns.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-fns.ts @@ -1,5 +1,7 @@ -import { getConfig } from "@app/lib/config/env"; import { z } from "zod"; + +import { getConfig } from "@app/lib/config/env"; + import { AcmeMalformedError } from "./pki-acme-errors"; export const buildUrl = (profileId: string, path: string): string => { diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts index e3a82000d8..1e7123353b 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -1,23 +1,4 @@ -import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; -import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; -import { crypto } from "@app/lib/crypto/cryptography"; -import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; -import { logger } from "@app/lib/logger"; -import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import * as x509 from "@peculiar/x509"; - -import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; -import { isPrivateIp } from "@app/lib/ip/ipRange"; -import { ActorType } from "@app/services/auth/auth-type"; -import { - EnrollmentType, - TCertificateProfileWithConfigs -} from "@app/services/certificate-profile/certificate-profile-types"; -import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; -import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { TProjectDALFactory } from "@app/services/project/project-dal"; -import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; import { calculateJwkThumbprint, errors, @@ -27,6 +8,26 @@ import { JWSHeaderParameters } from "jose"; import { z, ZodError } from "zod"; + +import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; +import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; +import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; +import { crypto } from "@app/lib/crypto/cryptography"; +import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; +import { isPrivateIp } from "@app/lib/ip/ipRange"; +import { logger } from "@app/lib/logger"; +import { ActorType } from "@app/services/auth/auth-type"; +import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; +import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; +import { + EnrollmentType, + TCertificateProfileWithConfigs +} from "@app/services/certificate-profile/certificate-profile-types"; +import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; +import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; + import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal"; import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal"; diff --git a/backend/src/ee/services/pki-acme/pki-acme-types.ts b/backend/src/ee/services/pki-acme/pki-acme-types.ts index 3132c98122..3ddb424f1b 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-types.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-types.ts @@ -1,6 +1,6 @@ +import { JWSHeaderParameters } from "jose"; import { z } from "zod"; -import { JWSHeaderParameters } from "jose"; import { AcmeOrderResourceSchema, CreateAcmeAccountBodySchema, diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index 96757dc22a..f594f41ffe 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -8,6 +8,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { removeTrailingSlash } from "@app/lib/fn"; import { containsGlobPatterns } from "@app/lib/picomatch"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; +import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; import { ApproverType, BypasserType } from "../access-approval-policy/access-approval-policy-types"; @@ -39,6 +40,7 @@ type TSecretApprovalPolicyServiceFactoryDep = { secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory; projectEnvDAL: Pick; userDAL: Pick; + projectMembershipDAL: Pick; secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory; secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory; licenseService: Pick; @@ -56,9 +58,25 @@ export const secretApprovalPolicyServiceFactory = ({ secretApprovalPolicyEnvironmentDAL, projectEnvDAL, userDAL, + projectMembershipDAL, licenseService, secretApprovalRequestDAL }: TSecretApprovalPolicyServiceFactoryDep) => { + const verifyProjectUserMembership = async (userIds: string[], orgId: string, projectId: string) => { + if (userIds.length === 0) return; + const projectMemberships = (await projectMembershipDAL.findProjectMembershipsByUserIds(orgId, userIds)).filter( + (v) => v.projectId === projectId + ); + + if (projectMemberships.length !== userIds.length) { + const projectMemberUserIds = new Set(projectMemberships.map((member) => member.userId)); + const userIdsNotInProject = userIds.filter((id) => !projectMemberUserIds.has(id)); + throw new BadRequestError({ + message: `Some users are not members of the project: ${userIdsNotInProject.join(", ")}` + }); + } + }; + const $policyExists = async ({ envIds, envId, @@ -202,6 +220,7 @@ export const secretApprovalPolicyServiceFactory = ({ }, tx ); + await secretApprovalPolicyEnvironmentDAL.insertMany( envs.map((env) => ({ envId: env.id, @@ -233,6 +252,8 @@ export const secretApprovalPolicyServiceFactory = ({ userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id)); } + await verifyProjectUserMembership(userApproverIds, actorOrgId, projectId); + await secretApprovalPolicyApproverDAL.insertMany( userApproverIds.map((approverUserId) => ({ approverUserId, @@ -250,6 +271,8 @@ export const secretApprovalPolicyServiceFactory = ({ ); if (bypasserUserIds.length) { + await verifyProjectUserMembership(bypasserUserIds, actorOrgId, projectId); + await secretApprovalPolicyBypasserDAL.insertMany( bypasserUserIds.map((userId) => ({ bypasserUserId: userId, @@ -425,6 +448,8 @@ export const secretApprovalPolicyServiceFactory = ({ userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id)); } + await verifyProjectUserMembership(userApproverIds, actorOrgId, secretApprovalPolicy.projectId); + await secretApprovalPolicyApproverDAL.insertMany( userApproverIds.map((approverUserId) => ({ approverUserId, @@ -458,6 +483,8 @@ export const secretApprovalPolicyServiceFactory = ({ await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx); if (bypasserUserIds.length) { + await verifyProjectUserMembership(bypasserUserIds, actorOrgId, secretApprovalPolicy.projectId); + await secretApprovalPolicyBypasserDAL.insertMany( bypasserUserIds.map((userId) => ({ bypasserUserId: userId, diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index db300720fa..2610d9324d 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -166,6 +166,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { tx.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer), tx.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"), tx.ref("comment").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerComment"), + tx.ref("createdAt").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerCreatedAt"), tx.ref("email").withSchema("secretApprovalReviewerUser").as("reviewerEmail"), tx.ref("username").withSchema("secretApprovalReviewerUser").as("reviewerUsername"), tx.ref("firstName").withSchema("secretApprovalReviewerUser").as("reviewerFirstName"), @@ -240,6 +241,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { reviewerUsername: username, reviewerFirstName: firstName, reviewerComment: comment, + reviewerCreatedAt: createdAt, reviewerIsOrgMembershipActive: isOrgMembershipActive }) => userId @@ -251,6 +253,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { lastName, username, comment: comment ?? "", + createdAt, isOrgMembershipActive } : undefined diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 2a8fbadace..927694ef40 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2309,7 +2309,10 @@ export const AppConnections = { code: "The OAuth code to use to connect with Azure Client Secrets.", tenantId: "The Tenant ID to use to connect with Azure Client Secrets.", clientId: "The Client ID to use to connect with Azure Client Secrets.", - clientSecret: "The Client Secret to use to connect with Azure Client Secrets." + clientSecret: "The Client Secret to use to connect with Azure Client Secrets.", + certificateBody: "The certificate body in PEM format to use to connect with Azure Client Secrets.", + privateKey: + "The private key to use to connect with Azure Client Secrets. This is never transmitted to Azure and is only used to sign the Azure client assertion with." }, AZURE_DEVOPS: { code: "The OAuth code to use to connect with Azure DevOps.", diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 70146c8543..7c2e3b326c 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -17,30 +17,30 @@ import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approva import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal"; import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service"; import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service"; -import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; -import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal"; import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue"; import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; +import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; +import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { certificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { certificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service"; import { certificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service"; -import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal"; -import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue"; -import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; +import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal"; +import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue"; +import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; import { eventBusFactory } from "@app/ee/services/event/event-bus-service"; import { sseServiceFactory } from "@app/ee/services/event/event-sse-service"; import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal"; import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service"; -import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; -import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; -import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal"; import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; +import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; +import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; +import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal"; import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal"; import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { groupDALFactory } from "@app/ee/services/group/group-dal"; @@ -74,9 +74,12 @@ import { pamSessionServiceFactory } from "@app/ee/services/pam-session/pam-sessi import { permissionDALFactory } from "@app/ee/services/permission/permission-dal"; import { permissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { pitServiceFactory } from "@app/ee/services/pit/pit-service"; +import { pkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; import { pkiAcmeAuthDALFactory } from "@app/ee/services/pki-acme/pki-acme-auth-dal"; +import { pkiAcmeChallengeDALFactory } from "@app/ee/services/pki-acme/pki-acme-challenge-dal"; import { pkiAcmeChallengeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-challenge-service"; import { pkiAcmeOrderAuthDALFactory } from "@app/ee/services/pki-acme/pki-acme-order-auth-dal"; +import { pkiAcmeOrderDALFactory } from "@app/ee/services/pki-acme/pki-acme-order-dal"; import { pkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-service"; import { projectTemplateDALFactory } from "@app/ee/services/project-template/project-template-dal"; import { projectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service"; @@ -102,39 +105,39 @@ import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret import { secretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { secretReplicationServiceFactory } from "@app/ee/services/secret-replication/secret-replication-service"; -import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; -import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; -import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; -import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; -import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; -import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; +import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; +import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; +import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; import { gitAppDALFactory } from "@app/ee/services/secret-scanning/git-app-dal"; import { gitAppInstallSessionDALFactory } from "@app/ee/services/secret-scanning/git-app-install-session-dal"; import { secretScanningDALFactory } from "@app/ee/services/secret-scanning/secret-scanning-dal"; import { secretScanningQueueFactory } from "@app/ee/services/secret-scanning/secret-scanning-queue"; import { secretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service"; +import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; +import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; +import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { snapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal"; import { snapshotSecretDALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal"; import { snapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; -import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; -import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; +import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; +import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; +import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; -import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; -import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; -import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; +import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; +import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; import { sshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { sshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal"; import { sshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service"; import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal"; -import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; -import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; -import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; +import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; +import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; +import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { subOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; @@ -154,12 +157,16 @@ import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { appConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; -import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; -import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { authDALFactory } from "@app/services/auth/auth-dal"; import { authLoginServiceFactory } from "@app/services/auth/auth-login-service"; import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service"; import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service"; +import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; +import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; +import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; +import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; +import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue"; @@ -172,18 +179,14 @@ import { internalCertificateAuthorityServiceFactory } from "@app/services/certif import { certificateEstV3ServiceFactory } from "@app/services/certificate-est-v3/certificate-est-v3-service"; import { certificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { certificateProfileServiceFactory } from "@app/services/certificate-profile/certificate-profile-service"; -import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; -import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal"; import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; +import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; +import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateV3QueueServiceFactory } from "@app/services/certificate-v3/certificate-v3-queue"; import { certificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; -import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; -import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; -import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { cmekServiceFactory } from "@app/services/cmek/cmek-service"; import { convertorServiceFactory } from "@app/services/convertor/convertor-service"; import { acmeEnrollmentConfigDALFactory } from "@app/services/enrollment-config/acme-enrollment-config-dal"; @@ -194,17 +197,21 @@ import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/externa import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue"; import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service"; import { vaultExternalMigrationConfigDALFactory } from "@app/services/external-migration/vault-external-migration-config-dal"; -import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal"; import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; -import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; +import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal"; import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal"; import { folderCommitQueueServiceFactory } from "@app/services/folder-commit/folder-commit-queue"; import { folderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service"; -import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; +import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal"; +import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { healthAlertServiceFactory } from "@app/services/health-alert/health-alert-queue"; +import { identityDALFactory } from "@app/services/identity/identity-dal"; +import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; +import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; +import { identityServiceFactory } from "@app/services/identity/identity-service"; import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal"; import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal"; @@ -234,27 +241,23 @@ import { identityTokenAuthServiceFactory } from "@app/services/identity-token-au import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal"; import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal"; import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; -import { identityDALFactory } from "@app/services/identity/identity-dal"; -import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; -import { identityServiceFactory } from "@app/services/identity/identity-service"; -import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; -import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { integrationDALFactory } from "@app/services/integration/integration-dal"; import { integrationServiceFactory } from "@app/services/integration/integration-service"; +import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; +import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { kmsServiceFactory } from "@app/services/kms/kms-service"; import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types"; +import { membershipDALFactory } from "@app/services/membership/membership-dal"; +import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal"; import { membershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; import { membershipIdentityDALFactory } from "@app/services/membership-identity/membership-identity-dal"; import { membershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service"; import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; import { membershipUserServiceFactory } from "@app/services/membership-user/membership-user-service"; -import { membershipDALFactory } from "@app/services/membership/membership-dal"; -import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal"; import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal"; @@ -263,11 +266,11 @@ import { notificationServiceFactory } from "@app/services/notification/notificat import { userNotificationDALFactory } from "@app/services/notification/user-notification-dal"; import { offlineUsageReportDALFactory } from "@app/services/offline-usage-report/offline-usage-report-dal"; import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service"; -import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; -import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal"; import { orgDALFactory } from "@app/services/org/org-dal"; import { orgServiceFactory } from "@app/services/org/org-service"; +import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; +import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { pamAccountRotationServiceFactory } from "@app/services/pam-account-rotation/pam-account-rotation-queue"; import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; @@ -289,6 +292,10 @@ import { pkiSyncQueueFactory } from "@app/services/pki-sync/pki-sync-queue"; import { pkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal"; import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; +import { projectDALFactory } from "@app/services/project/project-dal"; +import { projectQueueFactory } from "@app/services/project/project-queue"; +import { projectServiceFactory } from "@app/services/project/project-service"; +import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; import { projectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { projectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { projectEnvDALFactory } from "@app/services/project-env/project-env-dal"; @@ -297,18 +304,19 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal" import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; -import { projectDALFactory } from "@app/services/project/project-dal"; -import { projectQueueFactory } from "@app/services/project/project-queue"; -import { projectServiceFactory } from "@app/services/project/project-service"; -import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; -import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { reminderDALFactory } from "@app/services/reminder/reminder-dal"; import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue"; import { reminderServiceFactory } from "@app/services/reminder/reminder-service"; +import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { roleDALFactory } from "@app/services/role/role-dal"; import { roleServiceFactory } from "@app/services/role/role-service"; +import { secretDALFactory } from "@app/services/secret/secret-dal"; +import { secretQueueFactory } from "@app/services/secret/secret-queue"; +import { secretServiceFactory } from "@app/services/secret/secret-service"; +import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; +import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { secretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal"; import { secretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -328,11 +336,6 @@ import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret- import { secretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service"; import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { secretVersionV2TagBridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; -import { secretDALFactory } from "@app/services/secret/secret-dal"; -import { secretQueueFactory } from "@app/services/secret/secret-queue"; -import { secretServiceFactory } from "@app/services/secret/secret-service"; -import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; -import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal"; import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { projectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; @@ -348,18 +351,15 @@ import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-servi import { totpConfigDALFactory } from "@app/services/totp/totp-config-dal"; import { totpServiceFactory } from "@app/services/totp/totp-service"; import { upgradePathServiceFactory } from "@app/services/upgrade-path/upgrade-path-service"; -import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; -import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; import { userDALFactory } from "@app/services/user/user-dal"; import { userServiceFactory } from "@app/services/user/user-service"; +import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; +import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; import { webhookDALFactory } from "@app/services/webhook/webhook-dal"; import { webhookServiceFactory } from "@app/services/webhook/webhook-service"; import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal"; import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; -import { pkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; -import { pkiAcmeChallengeDALFactory } from "@app/ee/services/pki-acme/pki-acme-challenge-dal"; -import { pkiAcmeOrderDALFactory } from "@app/ee/services/pki-acme/pki-acme-order-dal"; import { injectAuditLogInfo } from "../plugins/audit-log"; import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege"; import { injectIdentity } from "../plugins/auth/inject-identity"; @@ -707,6 +707,7 @@ export const registerRoutes = async ( secretApprovalPolicyDAL, licenseService, userDAL, + projectMembershipDAL, secretApprovalRequestDAL }); @@ -1178,6 +1179,10 @@ export const registerRoutes = async ( apiEnrollmentConfigDAL, estEnrollmentConfigDAL, acmeEnrollmentConfigDAL, + certificateBodyDAL, + certificateSecretDAL, + certificateAuthorityDAL, + certificateAuthorityCertDAL, permissionService, kmsService, projectDAL @@ -1535,7 +1540,7 @@ export const registerRoutes = async ( accessApprovalRequestDAL, accessApprovalRequestReviewerDAL, additionalPrivilegeDAL, - membershipUserDAL + projectMembershipDAL }); const accessApprovalRequestService = accessApprovalRequestServiceFactory({ diff --git a/backend/src/server/routes/v1/certificate-profiles-router.ts b/backend/src/server/routes/v1/certificate-profiles-router.ts index 7d4e047731..5792c5e835 100644 --- a/backend/src/server/routes/v1/certificate-profiles-router.ts +++ b/backend/src/server/routes/v1/certificate-profiles-router.ts @@ -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 { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; import { CertStatus } from "@app/services/certificate/certificate-types"; +import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => { server.route({ @@ -498,6 +498,71 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid } }); + server.route({ + method: "GET", + url: "/:id/certificates/latest-active-bundle", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateProfiles], + description: "Get latest active certificate bundle for a profile", + params: z.object({ + id: z.string().uuid() + }), + response: { + 200: z.object({ + certificate: z.string().nullable(), + certificateChain: z.string().nullable(), + privateKey: z.string().nullable(), + serialNumber: z.string().nullable() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const response = await server.services.certificateProfile.getLatestActiveCertificateBundle({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + profileId: req.params.id + }); + + if (!response) { + return { + certificate: null, + certificateChain: null, + privateKey: null, + serialNumber: null + }; + } + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: response.certObj.projectId, + event: { + type: EventType.GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE, + metadata: { + certificateProfileId: response.profile.id, + certificateId: response.certObj.id, + commonName: response.certObj.commonName, + profileName: response.profile.slug, + serialNumber: response.certObj.serialNumber + } + } + }); + + return { + certificate: response.certificate, + certificateChain: response.certificateChain, + privateKey: response.privateKey, + serialNumber: response.certObj.serialNumber + }; + } + }); + server.route({ method: "GET", url: "/:id/acme/eab-secret/reveal", diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 70548395d3..e3838cddc2 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -162,7 +162,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { .default(InfisicalProjectTemplate.Default) .describe(PROJECTS.CREATE.template), type: z.nativeEnum(ProjectType).default(ProjectType.SecretManager), - shouldCreateDefaultEnvs: z.boolean().optional().default(true) + shouldCreateDefaultEnvs: z.boolean().optional().default(true), + hasDeleteProtection: z.boolean().optional().default(false) }), response: { 200: z.object({ @@ -183,7 +184,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { kmsKeyId: req.body.kmsKeyId, template: req.body.template, type: req.body.type, - createDefaultEnvs: req.body.shouldCreateDefaultEnvs + createDefaultEnvs: req.body.shouldCreateDefaultEnvs, + hasDeleteProtection: req.body.hasDeleteProtection }); await server.services.telemetry.sendPostHogEvents({ diff --git a/backend/src/server/routes/v2/deprecated-project-router.ts b/backend/src/server/routes/v2/deprecated-project-router.ts index 55493681a3..82b3513590 100644 --- a/backend/src/server/routes/v2/deprecated-project-router.ts +++ b/backend/src/server/routes/v2/deprecated-project-router.ts @@ -110,7 +110,8 @@ export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider .default(InfisicalProjectTemplate.Default) .describe(PROJECTS.CREATE.template), type: z.nativeEnum(ProjectType).default(ProjectType.SecretManager), - shouldCreateDefaultEnvs: z.boolean().optional().default(true) + shouldCreateDefaultEnvs: z.boolean().optional().default(true), + hasDeleteProtection: z.boolean().optional().default(false) }), response: { 200: z.object({ @@ -131,7 +132,8 @@ export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider kmsKeyId: req.body.kmsKeyId, template: req.body.template, type: req.body.type, - createDefaultEnvs: req.body.shouldCreateDefaultEnvs + createDefaultEnvs: req.body.shouldCreateDefaultEnvs, + hasDeleteProtection: req.body.hasDeleteProtection }); await server.services.telemetry.sendPostHogEvents({ diff --git a/backend/src/server/routes/v3/certificates-router.ts b/backend/src/server/routes/v3/certificates-router.ts index 9aa4b198b5..f590aa1118 100644 --- a/backend/src/server/routes/v3/certificates-router.ts +++ b/backend/src/server/routes/v3/certificates-router.ts @@ -6,6 +6,12 @@ import { ms } from "@app/lib/ms"; import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; 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 { CertExtendedKeyUsageType, @@ -16,12 +22,6 @@ import { extractCertificateRequestFromCSR } from "@app/services/certificate-comm 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 { - ACMESANType, - CertificateOrderStatus, - CertKeyAlgorithm, - CertSignatureAlgorithm -} from "@app/services/certificate/certificate-types"; interface CertificateRequestForService { commonName?: string; diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index 47c3c2cb8f..4ee4566c18 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -11,5 +11,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerDeprecatedSecretRouter, { prefix: "/secrets" }); await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" }); - await server.register(registerCertificatesRouter, { prefix: "/certificates" }); + await server.register(registerCertificatesRouter, { prefix: "/pki/certificates" }); }; diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts index eb0521c645..1f7fc808d8 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts @@ -1,4 +1,5 @@ export enum AzureClientSecretsConnectionMethod { OAuth = "oauth", - ClientSecret = "client-secret" + ClientSecret = "client-secret", + Certificate = "certificate" } diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index 22cec0ae7e..7916ac92db 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -1,9 +1,14 @@ /* eslint-disable no-case-declarations */ import { AxiosError, AxiosResponse } from "axios"; +import type { KeyObject } from "crypto"; +import RE2 from "re2"; +import { v4 as uuidv4 } from "uuid"; import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; +import { crypto } from "@app/lib/crypto"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; import { decryptAppConnectionCredentials, encryptAppConnectionCredentials, @@ -17,11 +22,82 @@ import { AppConnection } from "../app-connection-enums"; import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums"; import { ExchangeCodeAzureResponse, + TAzureClientSecretsConnectionCertificateCredentials, TAzureClientSecretsConnectionClientSecretCredentials, TAzureClientSecretsConnectionConfig, TAzureClientSecretsConnectionCredentials } from "./azure-client-secrets-connection-types"; +const generateClientAssertion = ( + clientId: string, + tenantId: string, + privateKey: string, + certificate: string +): string => { + const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + + const certBuffer = Buffer.from( + certificate + .replace(new RE2("-----BEGIN CERTIFICATE-----"), "") + .replace(new RE2("-----END CERTIFICATE-----"), "") + .replace(new RE2("\\s", "g"), ""), + "base64" + ); + + // thumbprint of the certificate is used for the jwt header + const thumbprint = crypto.nativeCrypto.createHash("sha1").update(certBuffer).digest("hex"); + const x5t = Buffer.from(thumbprint, "hex").toString("base64url"); + + // JWT Header + const header = { + alg: "RS256", + typ: "JWT", + x5t + }; + + const now = Math.floor(Date.now() / 1000); + const payload = { + aud: tokenEndpoint, + exp: now + 600, // expire the assertion in 10 minutes (not the access access token TTL, but rather the assertion TTL itself) + iss: clientId, + jti: uuidv4(), // random ID for the JWT + nbf: now, // not before the jwt is valid + sub: clientId + }; + + // encode header and payload + const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url"); + const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url"); + const signatureInput = `${encodedHeader}.${encodedPayload}`; + + let keyObject: KeyObject; + + try { + if (privateKey.includes("BEGIN PRIVATE KEY")) { + keyObject = crypto.nativeCrypto.createPrivateKey(privateKey); + } else { + // if user forgot to wrap in begin/end private key, decode and use as der format + keyObject = crypto.nativeCrypto.createPrivateKey({ + key: Buffer.from(privateKey, "base64"), + format: "der", + type: "pkcs8" + }); + } + } catch (error) { + throw new BadRequestError({ + message: "Invalid private key format provided. Expected PEM format private key." + }); + } + + // sign with private key + const signer = crypto.nativeCrypto.createSign("RSA-SHA256"); + signer.update(signatureInput); + signer.end(); + const signature = signer.sign(keyObject, "base64url"); + + return `${signatureInput}.${signature}`; +}; + export const getAzureClientSecretsConnectionListItem = () => { const { INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID } = getConfig(); @@ -30,7 +106,8 @@ export const getAzureClientSecretsConnectionListItem = () => { app: AppConnection.AzureClientSecrets as const, methods: Object.values(AzureClientSecretsConnectionMethod) as [ AzureClientSecretsConnectionMethod.OAuth, - AzureClientSecretsConnectionMethod.ClientSecret + AzureClientSecretsConnectionMethod.ClientSecret, + AzureClientSecretsConnectionMethod.Certificate ], oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID }; @@ -64,7 +141,7 @@ export const getAzureConnectionAccessToken = async ( const { refreshToken } = credentials; const currentTime = Date.now(); switch (appConnection.method) { - case AzureClientSecretsConnectionMethod.OAuth: + case AzureClientSecretsConnectionMethod.OAuth: { if ( !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET @@ -101,7 +178,8 @@ export const getAzureConnectionAccessToken = async ( await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials }); return data.access_token; - case AzureClientSecretsConnectionMethod.ClientSecret: + } + case AzureClientSecretsConnectionMethod.ClientSecret: { const accessTokenCredentials = (await decryptAppConnectionCredentials({ orgId: appConnection.orgId, projectId: appConnection.projectId, @@ -139,6 +217,50 @@ export const getAzureConnectionAccessToken = async ( await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials }); return clientData.access_token; + } + + case AzureClientSecretsConnectionMethod.Certificate: { + const accessTokenCredentials = (await decryptAppConnectionCredentials({ + orgId: appConnection.orgId, + projectId: appConnection.projectId, + kmsService, + encryptedCredentials: appConnection.encryptedCredentials + })) as TAzureClientSecretsConnectionCertificateCredentials; + const { accessToken, expiresAt, clientId, tenantId, certificateBody, privateKey } = accessTokenCredentials; + if (accessToken && expiresAt && expiresAt > currentTime + 300000) { + return accessToken; + } + + const clientAssertion = generateClientAssertion(clientId, tenantId, privateKey, certificateBody); + const { data: clientData } = await request.post( + IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"), + new URLSearchParams({ + grant_type: "client_credentials", + scope: `https://graph.microsoft.com/.default`, + client_id: clientId, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: clientAssertion + }) + ); + + const updatedClientCredentials = { + ...accessTokenCredentials, + accessToken: clientData.access_token, + expiresAt: currentTime + clientData.expires_in * 1000 + }; + + const encryptedClientCredentials = await encryptAppConnectionCredentials({ + credentials: updatedClientCredentials, + orgId: appConnection.orgId, + projectId: appConnection.projectId, + kmsService + }); + + await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials }); + + return clientData.access_token; + } + default: throw new InternalServerError({ message: `Unhandled Azure connection method: ${appConnection.method as AzureClientSecretsConnectionMethod}` @@ -156,7 +278,7 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA } = getConfig(); switch (method) { - case AzureClientSecretsConnectionMethod.OAuth: + case AzureClientSecretsConnectionMethod.OAuth: { if (!SITE_URL) { throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); } @@ -221,8 +343,9 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA refreshToken: tokenResp.data.refresh_token, expiresAt: Date.now() + tokenResp.data.expires_in * 1000 }; + } - case AzureClientSecretsConnectionMethod.ClientSecret: + case AzureClientSecretsConnectionMethod.ClientSecret: { const { tenantId, clientId, clientSecret } = inputCredentials; try { const { data: clientData } = await request.post( @@ -255,6 +378,57 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA }); } } + } + case AzureClientSecretsConnectionMethod.Certificate: { + const { tenantId, certificateBody, privateKey, clientId } = inputCredentials; + try { + const clientAssertion = generateClientAssertion(clientId, tenantId, privateKey, certificateBody); + + const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + + const params = new URLSearchParams({ + client_id: clientId, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: clientAssertion, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials" + }); + + const response = await request.post(tokenEndpoint, params.toString(), { + headers: { + "Content-Type": "application/x-www-form-urlencoded" + } + }); + + return { + tenantId, + clientId, + certificateBody, + privateKey, + accessToken: response.data.access_token, + expiresAt: Date.now() + response.data.expires_in * 1000 + }; + } catch (e: unknown) { + if (e instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to get access token: ${ + (e?.response?.data as { error_description?: string })?.error_description || "Unknown error" + }` + }); + } else if (e instanceof BadRequestError) { + throw e; + } else { + logger.error( + e, + "validateAzureClientSecretsConnectionCredentials: Failed to get access token using certificate authentication" + ); + throw new InternalServerError({ + message: "Failed to get access token" + }); + } + } + } + default: throw new InternalServerError({ message: `Unhandled Azure connection method: ${method as AzureClientSecretsConnectionMethod}` diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts index d9f178a06f..dd387894ed 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts @@ -48,6 +48,31 @@ export const AzureClientSecretsConnectionClientSecretInputCredentialsSchema = z. .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId) }); +export const AzureClientSecretsConnectionCertificateInputCredentialsSchema = z.object({ + tenantId: z + .string() + .uuid() + .trim() + .min(1, "Tenant ID required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId), + clientId: z + .string() + .uuid() + .trim() + .min(1, "Client ID required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientId), + certificateBody: z + .string() + .trim() + .min(1, "Certificate body required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.certificateBody), + privateKey: z + .string() + .trim() + .min(1, "Private Key required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.privateKey) +}); + export const AzureClientSecretsConnectionClientSecretOutputCredentialsSchema = z.object({ clientId: z.string(), clientSecret: z.string(), @@ -56,6 +81,15 @@ export const AzureClientSecretsConnectionClientSecretOutputCredentialsSchema = z expiresAt: z.number() }); +export const AzureClientSecretsConnectionCertificateOutputCredentialsSchema = z.object({ + clientId: z.string(), + tenantId: z.string(), + certificateBody: z.string(), + privateKey: z.string(), + accessToken: z.string(), + expiresAt: z.number() +}); + export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discriminatedUnion("method", [ z.object({ method: z @@ -72,6 +106,14 @@ export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discrimin credentials: AzureClientSecretsConnectionClientSecretInputCredentialsSchema.describe( AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials ) + }), + z.object({ + method: z + .literal(AzureClientSecretsConnectionMethod.Certificate) + .describe(AppConnections.CREATE(AppConnection.AzureClientSecrets).method), + credentials: AzureClientSecretsConnectionCertificateInputCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials + ) }) ]); @@ -84,7 +126,8 @@ export const UpdateAzureClientSecretsConnectionSchema = z credentials: z .union([ AzureClientSecretsConnectionOAuthInputCredentialsSchema, - AzureClientSecretsConnectionClientSecretInputCredentialsSchema + AzureClientSecretsConnectionClientSecretInputCredentialsSchema, + AzureClientSecretsConnectionCertificateInputCredentialsSchema ]) .optional() .describe(AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials) @@ -105,6 +148,10 @@ export const AzureClientSecretsConnectionSchema = z.intersection( z.object({ method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema + }), + z.object({ + method: z.literal(AzureClientSecretsConnectionMethod.Certificate), + credentials: AzureClientSecretsConnectionCertificateOutputCredentialsSchema }) ]) ); @@ -122,6 +169,13 @@ export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion( clientId: true, tenantId: true }) + }), + BaseAzureClientSecretsConnectionSchema.extend({ + method: z.literal(AzureClientSecretsConnectionMethod.Certificate), + credentials: AzureClientSecretsConnectionCertificateOutputCredentialsSchema.pick({ + tenantId: true, + clientId: true + }) }) ]); diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts index 1ad5a3411b..e8a66cbd93 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts @@ -4,6 +4,7 @@ import { DiscriminativePick } from "@app/lib/types"; import { AppConnection } from "../app-connection-enums"; import { + AzureClientSecretsConnectionCertificateOutputCredentialsSchema, AzureClientSecretsConnectionClientSecretOutputCredentialsSchema, AzureClientSecretsConnectionOAuthOutputCredentialsSchema, AzureClientSecretsConnectionSchema, @@ -35,6 +36,10 @@ export type TAzureClientSecretsConnectionClientSecretCredentials = z.infer< typeof AzureClientSecretsConnectionClientSecretOutputCredentialsSchema >; +export type TAzureClientSecretsConnectionCertificateCredentials = z.infer< + typeof AzureClientSecretsConnectionCertificateOutputCredentialsSchema +>; + export interface ExchangeCodeAzureResponse { token_type: string; scope: string; diff --git a/backend/src/services/certificate-profile/certificate-profile-dal.ts b/backend/src/services/certificate-profile/certificate-profile-dal.ts index 5296cb1720..6415145ce8 100644 --- a/backend/src/services/certificate-profile/certificate-profile-dal.ts +++ b/backend/src/services/certificate-profile/certificate-profile-dal.ts @@ -462,6 +462,24 @@ export const certificateProfileDALFactory = (db: TDbClient) => { } }; + const getLatestActiveCertificateForProfile = async (profileId: string, tx?: Knex) => { + try { + const now = new Date(); + + const certificate = await (tx || db)(TableName.Certificate) + .where("profileId", profileId) + .where("status", "active") + .where("notAfter", ">", now) + .whereNull("revokedAt") + .orderBy("createdAt", "desc") + .first(); + + return certificate; + } catch (error) { + throw new DatabaseError({ error, name: "Get latest active certificate by profile" }); + } + }; + const isProfileInUse = async (profileId: string, tx?: Knex) => { try { const doc = await (tx || db)(TableName.Certificate).where("profileId", profileId).count("*").first(); @@ -485,6 +503,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { countByProjectId, findByNameAndProjectId, getCertificatesByProfile, + getLatestActiveCertificateForProfile, isProfileInUse }; }; diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index ffca3c1cbb..9d9ab5947c 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -9,6 +9,10 @@ import type { TPermissionServiceFactory } from "@app/ee/services/permission/perm import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorType, AuthMethod } from "../auth/auth-type"; +import type { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; +import type { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; +import type { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; +import type { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import type { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import type { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; @@ -178,6 +182,54 @@ describe("CertificateProfileService", () => { transaction: vi.fn() } as unknown as Pick; + const mockCertificateBodyDAL = { + create: vi.fn(), + findById: vi.fn(), + updateById: vi.fn(), + deleteById: vi.fn(), + transaction: vi.fn(), + find: vi.fn(), + findOne: vi.fn(), + update: vi.fn(), + delete: vi.fn() + } as unknown as TCertificateBodyDALFactory; + + const mockCertificateSecretDAL = { + create: vi.fn(), + findById: vi.fn(), + updateById: vi.fn(), + deleteById: vi.fn(), + transaction: vi.fn(), + find: vi.fn(), + findOne: vi.fn(), + update: vi.fn(), + delete: vi.fn() + } as unknown as TCertificateSecretDALFactory; + + const mockCertificateAuthorityDAL = { + create: vi.fn(), + findById: vi.fn(), + updateById: vi.fn(), + deleteById: vi.fn(), + transaction: vi.fn(), + find: vi.fn(), + findOne: vi.fn(), + update: vi.fn(), + delete: vi.fn() + } as unknown as TCertificateAuthorityDALFactory; + + const mockCertificateAuthorityCertDAL = { + create: vi.fn(), + findById: vi.fn(), + updateById: vi.fn(), + deleteById: vi.fn(), + transaction: vi.fn(), + find: vi.fn(), + findOne: vi.fn(), + update: vi.fn(), + delete: vi.fn() + } as unknown as TCertificateAuthorityCertDALFactory; + beforeEach(() => { vi.spyOn(ForbiddenError, "from").mockReturnValue({ throwUnlessCan: vi.fn() @@ -195,6 +247,10 @@ describe("CertificateProfileService", () => { apiEnrollmentConfigDAL: mockApiEnrollmentConfigDAL, estEnrollmentConfigDAL: mockEstEnrollmentConfigDAL, acmeEnrollmentConfigDAL: mockAcmeEnrollmentConfigDAL, + certificateBodyDAL: mockCertificateBodyDAL, + certificateSecretDAL: mockCertificateSecretDAL, + certificateAuthorityDAL: mockCertificateAuthorityDAL, + certificateAuthorityCertDAL: mockCertificateAuthorityCertDAL, permissionService: mockPermissionService, kmsService: mockKmsService, projectDAL: mockProjectDAL diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index 8adbf2c45d..66a23a0e79 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -4,17 +4,24 @@ import * as x509 from "@peculiar/x509"; import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { + ProjectPermissionCertificateActions, ProjectPermissionCertificateProfileActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { buildUrl } from "@app/ee/services/pki-acme/pki-acme-fns"; 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 { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; +import { getCertificateCredentials, isCertChainValid } from "../certificate/certificate-fns"; +import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; +import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; +import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; -import { isCertChainValid } from "../certificate/certificate-fns"; +import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; import { TAcmeConfigData, TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; import { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal"; @@ -30,8 +37,6 @@ import { TCertificateProfileUpdate, TCertificateProfileWithConfigs } from "./certificate-profile-types"; -import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; -import { buildUrl } from "@app/ee/services/pki-acme/pki-acme-fns"; const generateAndEncryptAcmeEabSecret = async ( projectId: string, @@ -142,6 +147,10 @@ type TCertificateProfileServiceFactoryDep = { apiEnrollmentConfigDAL: TApiEnrollmentConfigDALFactory; estEnrollmentConfigDAL: TEstEnrollmentConfigDALFactory; acmeEnrollmentConfigDAL: TAcmeEnrollmentConfigDALFactory; + certificateBodyDAL: Pick; + certificateSecretDAL: Pick; + certificateAuthorityDAL: Pick; + certificateAuthorityCertDAL: Pick; permissionService: Pick; kmsService: Pick; projectDAL: Pick; @@ -162,6 +171,8 @@ export const certificateProfileServiceFactory = ({ apiEnrollmentConfigDAL, estEnrollmentConfigDAL, acmeEnrollmentConfigDAL, + certificateBodyDAL, + certificateSecretDAL, permissionService, kmsService, projectDAL @@ -729,6 +740,106 @@ export const certificateProfileServiceFactory = ({ return certificates; }; + const getLatestActiveCertificateBundle = async ({ + actor, + actorId, + actorAuthMethod, + actorOrgId, + profileId + }: { + actor: ActorType; + actorId: string; + actorAuthMethod: ActorAuthMethod; + actorOrgId: string; + profileId: string; + }) => { + 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 + ); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateActions.Read, + ProjectPermissionSub.Certificates + ); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateActions.ReadPrivateKey, + ProjectPermissionSub.Certificates + ); + + const cert = await certificateProfileDAL.getLatestActiveCertificateForProfile(profileId); + + if (!cert) { + return null; + } + + const certBody = await certificateBodyDAL.findOne({ certId: cert.id }); + + const certificateManagerKeyId = await getProjectKmsCertificateKeyId({ + projectId: cert.projectId, + projectDAL, + kmsService + }); + + const kmsDecryptor = await kmsService.decryptWithKmsKey({ + kmsId: certificateManagerKeyId + }); + const decryptedCert = await kmsDecryptor({ + cipherTextBlob: certBody.encryptedCertificate + }); + + const certObj = new x509.X509Certificate(decryptedCert); + const certificate = certObj.toString("pem"); + + const decryptedCertChain = await kmsDecryptor({ + cipherTextBlob: certBody.encryptedCertificateChain! + }); + + const certificateChain = decryptedCertChain.toString(); + + let privateKey = null; + try { + const { certPrivateKey } = await getCertificateCredentials({ + certId: cert.id, + projectId: cert.projectId, + certificateSecretDAL, + projectDAL, + kmsService + }); + privateKey = certPrivateKey; + } catch (error) { + // Private key might not exist for ACME certificates or other external workflows + // where the key is generated client-side + if (error instanceof NotFoundError) { + privateKey = null; + } else { + throw error; + } + } + + return { + certificate, + certificateChain, + privateKey, + profile, + certObj: cert + }; + }; + const getEstConfigurationByProfile = async ( params: | { @@ -854,6 +965,7 @@ export const certificateProfileServiceFactory = ({ listProfiles, deleteProfile, getProfileCertificates, + getLatestActiveCertificateBundle, getEstConfigurationByProfile, revealAcmeEabSecret }; diff --git a/backend/src/services/certificate-template-v2/certificate-template-v2-service.test.ts b/backend/src/services/certificate-template-v2/certificate-template-v2-service.test.ts index daacc5dded..f73d516a6e 100644 --- a/backend/src/services/certificate-template-v2/certificate-template-v2-service.test.ts +++ b/backend/src/services/certificate-template-v2/certificate-template-v2-service.test.ts @@ -613,39 +613,6 @@ describe("CertificateTemplateV2Service", () => { expect(result.isValid).toBe(true); }); - it("should handle camelCase key usage mapping correctly", async () => { - const templateWithOptionalUsages = { - ...sampleTemplate, - keyUsages: { - requiredUsages: { - all: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION, CertKeyUsageType.KEY_AGREEMENT] - }, - optionalUsages: { all: [CertKeyUsageType.CRL_SIGN, CertKeyUsageType.DECIPHER_ONLY] } - }, - extendedKeyUsages: { - requiredUsages: { all: [CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.CODE_SIGNING] }, - optionalUsages: { all: [CertExtendedKeyUsageType.SERVER_AUTH, CertExtendedKeyUsageType.OCSP_SIGNING] } - } - }; - mockCertificateTemplateV2DAL.findById.mockResolvedValue(templateWithOptionalUsages); - - const requestWithCamelCaseUsages = { - ...validRequest, - keyUsages: [ - CertKeyUsageType.DIGITAL_SIGNATURE, - CertKeyUsageType.NON_REPUDIATION, - CertKeyUsageType.KEY_AGREEMENT, - CertKeyUsageType.CRL_SIGN, - CertKeyUsageType.DECIPHER_ONLY - ], - extendedKeyUsages: [CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.CODE_SIGNING] - }; - - const result = await service.validateCertificateRequest("template-123", requestWithCamelCaseUsages); - expect(result.isValid).toBe(true); - expect(result.errors).toHaveLength(0); - }); - it("should validate wildcard patterns in allow attributes", async () => { const wildcardTemplate = { ...sampleTemplate, diff --git a/backend/src/services/certificate-template-v2/certificate-template-v2-service.ts b/backend/src/services/certificate-template-v2/certificate-template-v2-service.ts index 0046ae0e54..656c12e64b 100644 --- a/backend/src/services/certificate-template-v2/certificate-template-v2-service.ts +++ b/backend/src/services/certificate-template-v2/certificate-template-v2-service.ts @@ -482,7 +482,7 @@ export const certificateTemplateV2ServiceFactory = ({ } // Check ALLOWED key usages - if present, all usages must be in allowed list - if (request.keyUsages && keyUsagePolicy && keyUsagePolicy.allowed && keyUsagePolicy.allowed.length > 0) { + if (request.keyUsages && keyUsagePolicy) { const allAllowedUsages = [...(keyUsagePolicy.required || []), ...(keyUsagePolicy.allowed || [])]; const invalidUsages = request.keyUsages.filter((usage) => !allAllowedUsages.includes(usage)); if (invalidUsages.length > 0) { @@ -517,12 +517,7 @@ export const certificateTemplateV2ServiceFactory = ({ } // Check ALLOWED extended key usages - if present, all usages must be in allowed list - if ( - request.extendedKeyUsages && - extendedKeyUsagePolicy && - extendedKeyUsagePolicy.allowed && - extendedKeyUsagePolicy.allowed.length > 0 - ) { + if (request.extendedKeyUsages && extendedKeyUsagePolicy) { const allAllowedExtendedUsages = [ ...(extendedKeyUsagePolicy.required || []), ...(extendedKeyUsagePolicy.allowed || []) diff --git a/backend/src/services/certificate-v3/certificate-v3-service.test.ts b/backend/src/services/certificate-v3/certificate-v3-service.test.ts index 67c3b268b2..6c664e324f 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.test.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.test.ts @@ -7,6 +7,7 @@ import { ForbiddenError } from "@casl/ability"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; +import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; @@ -30,7 +31,6 @@ import { extractCertificateRequestFromCSR } from "../certificate-common/certificate-csr-utils"; import { certificateV3ServiceFactory, TCertificateV3ServiceFactory } from "./certificate-v3-service"; -import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; vi.mock("../certificate-common/certificate-csr-utils", () => ({ extractCertificateRequestFromCSR: vi.fn(), diff --git a/backend/src/services/certificate-v3/certificate-v3-service.ts b/backend/src/services/certificate-v3/certificate-v3-service.ts index 7f4c6c637e..7b6538ab24 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.ts @@ -9,6 +9,7 @@ import { ProjectPermissionCertificateProfileActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; @@ -64,7 +65,6 @@ import { TSignCertificateFromProfileDTO, TUpdateRenewalConfigDTO } from "./certificate-v3-types"; -import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; type TCertificateV3ServiceFactoryDep = { certificateDAL: Pick; @@ -543,6 +543,10 @@ export const certificateV3ServiceFactory = ({ const { keyAlgorithm: extractedKeyAlgorithm, signatureAlgorithm: extractedSignatureAlgorithm } = extractAlgorithmsFromCSR(csr); + mappedCertificateRequest.keyAlgorithm = extractedKeyAlgorithm; + mappedCertificateRequest.signatureAlgorithm = extractedSignatureAlgorithm; + mappedCertificateRequest.validity = validity; + const validationResult = await certificateTemplateV2Service.validateCertificateRequest( profile.certificateTemplateId, mappedCertificateRequest @@ -675,7 +679,7 @@ export const certificateV3ServiceFactory = ({ status: CertificateOrderStatus.VALID })), authorizations: [], - finalize: `/api/v3/certificates/orders/${orderId}/completed`, + finalize: `/api/v3/pki/certificates/orders/${orderId}/completed`, certificate: certificateResult.certificate, projectId: certificateResult.projectId, profileName: certificateResult.profileName diff --git a/backend/src/services/certificate-v3/certificate-v3-types.ts b/backend/src/services/certificate-v3/certificate-v3-types.ts index 8685d5f30e..8a2cf70f77 100644 --- a/backend/src/services/certificate-v3/certificate-v3-types.ts +++ b/backend/src/services/certificate-v3/certificate-v3-types.ts @@ -1,12 +1,12 @@ import { TProjectPermission } from "@app/lib/types"; +import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types"; import { CertExtendedKeyUsageType, CertKeyUsageType, CertSubjectAlternativeNameType } from "../certificate-common/certificate-constants"; import { EnrollmentType } from "../certificate-profile/certificate-profile-types"; -import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types"; export type TIssueCertificateFromProfileDTO = { profileId: string; diff --git a/backend/src/services/enrollment-config/enrollment-config-types.ts b/backend/src/services/enrollment-config/enrollment-config-types.ts index ed2f921c4b..7fe5a475d0 100644 --- a/backend/src/services/enrollment-config/enrollment-config-types.ts +++ b/backend/src/services/enrollment-config/enrollment-config-types.ts @@ -1,3 +1,8 @@ +import { + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate +} from "@app/db/schemas/pki-acme-enrollment-configs"; import { TPkiApiEnrollmentConfigs, TPkiApiEnrollmentConfigsInsert, @@ -8,11 +13,6 @@ import { TPkiEstEnrollmentConfigsInsert, TPkiEstEnrollmentConfigsUpdate } from "@app/db/schemas/pki-est-enrollment-configs"; -import { - TPkiAcmeEnrollmentConfigs, - TPkiAcmeEnrollmentConfigsInsert, - TPkiAcmeEnrollmentConfigsUpdate -} from "@app/db/schemas/pki-acme-enrollment-configs"; export type TEstEnrollmentConfig = TPkiEstEnrollmentConfigs; export type TEstEnrollmentConfigInsert = TPkiEstEnrollmentConfigsInsert; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 727cc9aa9d..188c985fb7 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -252,7 +252,8 @@ export const projectServiceFactory = ({ tx: trx, createDefaultEnvs = true, template = InfisicalProjectTemplate.Default, - type = ProjectType.SecretManager + type = ProjectType.SecretManager, + hasDeleteProtection }: TCreateProjectDTO) => { const organization = await orgDAL.findOne({ id: actorOrgId }); const { permission } = await permissionService.getOrgPermission({ @@ -325,7 +326,8 @@ export const projectServiceFactory = ({ slug, kmsSecretManagerKeyId: kmsKeyId, version: ProjectVersion.V3, - pitVersionLimit: 10 + pitVersionLimit: 10, + hasDeleteProtection }, tx ); @@ -587,6 +589,7 @@ export const projectServiceFactory = ({ actorOrgId, actionProjectType: ActionProjectType.Any }); + return project; }; diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 9e07452366..2d27c7c446 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -51,6 +51,7 @@ export type TCreateProjectDTO = { pitVersionLimit?: number; tx?: Knex; type?: ProjectType; + hasDeleteProtection?: boolean; }; export type TDeleteProjectBySlugDTO = { diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f12d446ef0..5c3661cb7a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -26,8 +26,6 @@ services: restart: always environment: - ALLOW_EMPTY_PASSWORD=yes - ports: - - 6379:6379 networks: - infisical volumes: diff --git a/docs/api-reference/endpoints/certificate-authorities/issue-cert.mdx b/docs/api-reference/endpoints/certificate-authorities/issue-cert.mdx deleted file mode 100644 index 045cada589..0000000000 --- a/docs/api-reference/endpoints/certificate-authorities/issue-cert.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Issue certificate" -openapi: "POST /api/v1/pki/ca/{caId}/issue-certificate" ---- diff --git a/docs/api-reference/endpoints/certificate-authorities/sign-cert.mdx b/docs/api-reference/endpoints/certificate-authorities/sign-cert.mdx deleted file mode 100644 index 95c8d8c652..0000000000 --- a/docs/api-reference/endpoints/certificate-authorities/sign-cert.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Sign certificate" -openapi: "POST /api/v1/pki/ca/{caId}/sign-certificate" ---- diff --git a/docs/api-reference/endpoints/certificate-profiles/get-latest-active-bundle.mdx b/docs/api-reference/endpoints/certificate-profiles/get-latest-active-bundle.mdx new file mode 100644 index 0000000000..aa033418db --- /dev/null +++ b/docs/api-reference/endpoints/certificate-profiles/get-latest-active-bundle.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Latest Active Certificate Bundle" +openapi: "GET /api/v1/pki/certificate-profiles/{id}/certificates/latest-active-bundle" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/certificates/issue-certificate.mdx b/docs/api-reference/endpoints/certificates/issue-certificate.mdx index 90a79a4af9..13a464b67f 100644 --- a/docs/api-reference/endpoints/certificates/issue-certificate.mdx +++ b/docs/api-reference/endpoints/certificates/issue-certificate.mdx @@ -1,4 +1,4 @@ --- title: "Issue Certificate" -openapi: "POST /api/v1/pki/certificates/issue-certificate" +openapi: "POST /api/v3/pki/certificates/issue-certificate" --- diff --git a/docs/api-reference/endpoints/certificates/renew.mdx b/docs/api-reference/endpoints/certificates/renew.mdx new file mode 100644 index 0000000000..b444243698 --- /dev/null +++ b/docs/api-reference/endpoints/certificates/renew.mdx @@ -0,0 +1,4 @@ +--- +title: "Renew Certificate" +openapi: "POST /api/v3/pki/certificates/{certificateId}/renew" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/certificates/sign-certificate.mdx b/docs/api-reference/endpoints/certificates/sign-certificate.mdx index 3132d5846f..7291025fc2 100644 --- a/docs/api-reference/endpoints/certificates/sign-certificate.mdx +++ b/docs/api-reference/endpoints/certificates/sign-certificate.mdx @@ -1,4 +1,4 @@ --- title: "Sign Certificate" -openapi: "POST /api/v1/pki/certificates/sign-certificate" +openapi: "POST /api/v3/pki/certificates/sign-certificate" --- diff --git a/docs/api-reference/endpoints/certificates/update-config.mdx b/docs/api-reference/endpoints/certificates/update-config.mdx new file mode 100644 index 0000000000..70520bf683 --- /dev/null +++ b/docs/api-reference/endpoints/certificates/update-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Certificate Config" +openapi: "PATCH /api/v3/pki/certificates/{certificateId}/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/pki/syncs/add-certificates.mdx b/docs/api-reference/endpoints/pki/syncs/add-certificates.mdx new file mode 100644 index 0000000000..c7b21996e0 --- /dev/null +++ b/docs/api-reference/endpoints/pki/syncs/add-certificates.mdx @@ -0,0 +1,4 @@ +--- +title: "Add Certificates to Sync" +openapi: "POST /api/v1/pki/syncs/{pkiSyncId}/certificates" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/pki/syncs/list-certificates.mdx b/docs/api-reference/endpoints/pki/syncs/list-certificates.mdx new file mode 100644 index 0000000000..eaece0a2d5 --- /dev/null +++ b/docs/api-reference/endpoints/pki/syncs/list-certificates.mdx @@ -0,0 +1,4 @@ +--- +title: "List Sync Certificates" +openapi: "GET /api/v1/pki/syncs/{pkiSyncId}/certificates" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/pki/syncs/remove-certificates.mdx b/docs/api-reference/endpoints/pki/syncs/remove-certificates.mdx new file mode 100644 index 0000000000..99c8bfe28c --- /dev/null +++ b/docs/api-reference/endpoints/pki/syncs/remove-certificates.mdx @@ -0,0 +1,4 @@ +--- +title: "Remove Certificates from Sync" +openapi: "DELETE /api/v1/pki/syncs/{pkiSyncId}/certificates" +--- \ No newline at end of file diff --git a/docs/cli/commands/gateway.mdx b/docs/cli/commands/gateway.mdx index 59202e46e4..32612938a2 100644 --- a/docs/cli/commands/gateway.mdx +++ b/docs/cli/commands/gateway.mdx @@ -6,12 +6,12 @@ description: "Run the Infisical gateway or manage its systemd service" ```bash - infisical gateway start --name= --relay= --auth-method= + sudo infisical gateway start --name= --auth-method= ``` ```bash - sudo infisical gateway systemd install --token= --domain= --name= --relay= + sudo infisical gateway systemd install --token= --domain= --name= ``` @@ -33,22 +33,27 @@ If you are moving from Gateway v1 to Gateway v2, this is NOT a drop-in switch. G - Run the Infisical gateway component within your the network where your target resources are located. The gateway establishes an SSH reverse tunnel to the specified relay server and provides secure access to private resources within your network. + Run the Infisical gateway component within your the network where your target resources are located. The gateway establishes an SSH reverse tunnel to a relay server and provides secure access to private resources within your network. ```bash -infisical gateway start --relay= --name= --auth-method= +sudo infisical gateway start --name= --auth-method= ``` + + By default, the gateway automatically connects to the relay with the lowest latency. To target a specific relay, use the `--relay=` flag. + + Once started, the gateway component will: -- Establish outbound SSH reverse tunnels to relay servers (no inbound firewall rules needed) +- Automatically connect to a healthy relay with the lowest latency (unless the `--relay` flag is specified) +- Establish outbound SSH reverse tunnel to relay server (no inbound firewall rules needed) - Authenticate using SSH certificates issued by Infisical - Automatically reconnect if the connection is lost - Provide access to private resources within your network ### Authentication -The Relay supports multiple authentication methods. Below are the available authentication methods, with their respective flags. +The Gateway supports multiple authentication methods. Below are the available authentication methods, with their respective flags. @@ -69,7 +74,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=universal-auth --client-id= --client-secret= --relay= --name= + sudo infisical gateway start --auth-method=universal-auth --client-id= --client-secret= --name= ``` @@ -93,7 +98,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=kubernetes --machine-identity-id= --relay= --name= + sudo infisical gateway start --auth-method=kubernetes --machine-identity-id= --name= ``` @@ -114,7 +119,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=azure --machine-identity-id= --relay= --name= + sudo infisical gateway start --auth-method=azure --machine-identity-id= --name= ``` @@ -135,7 +140,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=gcp-id-token --machine-identity-id= --relay= --name= + sudo infisical gateway start --auth-method=gcp-id-token --machine-identity-id= --name= ``` @@ -157,7 +162,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=gcp-iam --machine-identity-id= --service-account-key-file-path= --relay= --name= + sudo infisical gateway start --auth-method=gcp-iam --machine-identity-id= --service-account-key-file-path= --name= ``` @@ -176,7 +181,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=aws-iam --machine-identity-id= --relay= --name= + sudo infisical gateway start --auth-method=aws-iam --machine-identity-id= --name= ``` @@ -198,7 +203,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=oidc-auth --machine-identity-id= --jwt= --relay= --name= + sudo infisical gateway start --auth-method=oidc-auth --machine-identity-id= --jwt= --name= ``` @@ -222,7 +227,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --auth-method=jwt-auth --jwt= --machine-identity-id= --relay= --name= + sudo infisical gateway start --auth-method=jwt-auth --jwt= --machine-identity-id= --name= ``` @@ -238,7 +243,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash - infisical gateway start --token= --relay= --name= + sudo infisical gateway start --token= --name= ``` @@ -250,12 +255,14 @@ The Relay supports multiple authentication methods. Below are the available auth The name of the relay that this gateway should connect to. The relay must be running and registered before starting the gateway. + If this flag is omitted, the gateway will automatically connect to a healthy relay with the lowest latency. + ```bash # Example - infisical gateway start --relay=my-relay --name=my-gateway --token= + sudo infisical gateway start --relay=my-relay --name=my-gateway --token= ``` - **Note:** If using organization relays or self-hosted instance relays, you must first start a relay server using `infisical relay start` before connecting gateways to it. For Infisical Cloud users using instance relays, the relay infrastructure is already running and managed by Infisical. + **Note:** For Infisical Cloud users using instance relays, the relay infrastructure is already running and managed by Infisical. If using organization relays or self-hosted instance relays, you must first start a relay server. For more information on deploying relays, refer to the [Relay Deployment Guide](/documentation/platform/gateways/relay-deployment). @@ -264,7 +271,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash # Example - infisical gateway start --name=my-gateway --relay=my-relay --token= + sudo infisical gateway start --name=my-gateway --token= ``` @@ -274,7 +281,7 @@ The Relay supports multiple authentication methods. Below are the available auth ```bash # Example - infisical gateway start --domain=https://app.your-domain.com --relay= --name= + sudo infisical gateway start --domain=https://app.your-domain.com --name= ``` @@ -285,7 +292,7 @@ The Relay supports multiple authentication methods. Below are the available auth Install and enable the gateway as a systemd service. This command must be run with sudo on Linux. ```bash -sudo infisical gateway systemd install --token= --domain= --name= --relay= +sudo infisical gateway systemd install --token= --domain= --name= ``` ### Requirements @@ -302,7 +309,7 @@ sudo infisical gateway systemd install --token= --domain= --name= ```bash # Example - sudo infisical gateway systemd install --token= --name= --relay= + sudo infisical gateway systemd install --token= --name= ``` You may also expose the token to the CLI by setting the environment variable `INFISICAL_TOKEN` before executing the install command. @@ -314,7 +321,7 @@ sudo infisical gateway systemd install --token= --domain= --name= ```bash # Example - sudo infisical gateway systemd install --domain=https://app.your-domain.com --name= --relay= + sudo infisical gateway systemd install --domain=https://app.your-domain.com --name= ``` @@ -324,19 +331,23 @@ sudo infisical gateway systemd install --token= --domain= --name= ```bash # Example - sudo infisical gateway systemd install --name=my-gateway --token= --relay= + sudo infisical gateway systemd install --name=my-gateway --token= ``` - The name of the relay that this gateway should connect to. + The name of the relay that this gateway should connect to. The relay must be running and registered before starting the gateway. + + If this flag is omitted, the gateway will automatically connect to a healthy relay with the lowest latency. ```bash # Example sudo infisical gateway systemd install --relay=my-relay --token= --name= ``` + **Note:** For Infisical Cloud users using instance relays, the relay infrastructure is already running and managed by Infisical. If using organization relays or self-hosted instance relays, you must first start a relay server. For more information on deploying relays, refer to the [Relay Deployment Guide](/documentation/platform/gateways/relay-deployment). + @@ -671,3 +682,14 @@ sudo systemctl disable infisical-gateway # Disable auto-start on boot + +## Frequently Asked Questions + + + + If the `--relay` flag is omitted, the gateway automatically selects the optimal relay. It first checks for healthy organization relays and connects to the one with the lowest latency. If no organization relays are available, it then performs the same latency-based selection among the available managed relays. + + + No. The first time the gateway starts, it selects the optimal relay (based on latency) and caches that selection. On subsequent restarts, it will prioritize connecting to the cached relay. If it's unable to connect, it will then re-evaluate and connect to the next most optimal relay available. + + diff --git a/docs/docs.json b/docs/docs.json index 0b55fc6e32..db4c525270 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -705,37 +705,69 @@ "item": "Infisical PKI", "groups": [ { - "group": "Infisical PKI", + "group": "Certificate Management", "pages": [ "documentation/platform/pki/overview", - "documentation/platform/pki/private-ca", - "documentation/platform/pki/external-ca", - "documentation/platform/pki/subscribers", - "documentation/platform/pki/certificates", - "documentation/platform/pki/acme-ca", - "documentation/platform/pki/azure-adcs", - "documentation/platform/pki/est", - "documentation/platform/pki/alerting", { - "group": "Integrations", + "group": "Concepts", "pages": [ - "documentation/platform/pki/pki-issuer", - "documentation/platform/pki/integration-guides/gloo-mesh" + "documentation/platform/pki/concepts/certificate-mgmt", + "documentation/platform/pki/concepts/certificate-lifecycle" + ] + } + ] + }, + { + "group": "Product Reference", + "pages": [ + { + "group": "Certificate Authorities", + "pages": [ + "documentation/platform/pki/ca/overview", + "documentation/platform/pki/ca/private-ca", + "documentation/platform/pki/ca/external-ca" ] }, { - "group": "Certificate Syncs", + "group": "Certificates", "pages": [ - "documentation/platform/pki/certificate-syncs/overview", - { - "group": "Syncs", - "pages": [ - "documentation/platform/pki/certificate-syncs/aws-certificate-manager", - "documentation/platform/pki/certificate-syncs/azure-key-vault" - ] - } + "documentation/platform/pki/certificates/overview", + "documentation/platform/pki/certificates/profiles", + "documentation/platform/pki/certificates/templates", + "documentation/platform/pki/certificates/certificates" ] - } + }, + { + "group": "Enrollment Methods", + "pages": [ + "documentation/platform/pki/enrollment-methods/overview", + "documentation/platform/pki/enrollment-methods/api", + "documentation/platform/pki/enrollment-methods/est" + ] + }, + "documentation/platform/pki/alerting" + ] + }, + { + "group": "Infrastructure Integrations", + "pages": [ + "documentation/platform/pki/pki-issuer", + "documentation/platform/pki/integration-guides/gloo-mesh" + ] + }, + { + "group": "Certificate Syncs", + "pages": [ + "documentation/platform/pki/certificate-syncs/overview", + "documentation/platform/pki/certificate-syncs/aws-certificate-manager", + "documentation/platform/pki/certificate-syncs/azure-key-vault" + ] + }, + { + "group": "External CA Integrations", + "pages": [ + "documentation/platform/pki/ca/acme-ca", + "documentation/platform/pki/ca/azure-adcs" ] } ] @@ -2552,8 +2584,6 @@ "api-reference/endpoints/certificate-authorities/cert", "api-reference/endpoints/certificate-authorities/sign-intermediate", "api-reference/endpoints/certificate-authorities/import-cert", - "api-reference/endpoints/certificate-authorities/issue-cert", - "api-reference/endpoints/certificate-authorities/sign-cert", "api-reference/endpoints/certificate-authorities/crl" ] }, @@ -2562,13 +2592,15 @@ "pages": [ "api-reference/endpoints/certificates/list", "api-reference/endpoints/certificates/read", + "api-reference/endpoints/certificates/issue-certificate", + "api-reference/endpoints/certificates/sign-certificate", + "api-reference/endpoints/certificates/renew", + "api-reference/endpoints/certificates/update-config", "api-reference/endpoints/certificates/revoke", "api-reference/endpoints/certificates/delete", "api-reference/endpoints/certificates/cert-body", "api-reference/endpoints/certificates/bundle", - "api-reference/endpoints/certificates/private-key", - "api-reference/endpoints/certificates/issue-certificate", - "api-reference/endpoints/certificates/sign-certificate" + "api-reference/endpoints/certificates/private-key" ] }, { @@ -2605,10 +2637,14 @@ { "group": "Certificate Profiles", "pages": [ + "api-reference/endpoints/certificate-profiles/list", "api-reference/endpoints/certificate-profiles/create", "api-reference/endpoints/certificate-profiles/update", "api-reference/endpoints/certificate-profiles/get-by-id", - "api-reference/endpoints/certificate-profiles/delete" + "api-reference/endpoints/certificate-profiles/get-by-slug", + "api-reference/endpoints/certificate-profiles/delete", + "api-reference/endpoints/certificate-profiles/list-certificates", + "api-reference/endpoints/certificate-profiles/get-latest-active-bundle" ] }, { @@ -2617,6 +2653,9 @@ "api-reference/endpoints/pki/syncs/list", "api-reference/endpoints/pki/syncs/get-by-id", "api-reference/endpoints/pki/syncs/options", + "api-reference/endpoints/pki/syncs/list-certificates", + "api-reference/endpoints/pki/syncs/add-certificates", + "api-reference/endpoints/pki/syncs/remove-certificates", { "group": "AWS Certificate Manager", "pages": [ @@ -2988,6 +3027,30 @@ { "source": "/sdks/languages/csharp", "destination": "/sdks/languages/dotnet" + }, + { + "source": "/documentation/platform/pki/private-ca", + "destination": "/documentation/platform/pki/ca/private-ca" + }, + { + "source": "/documentation/platform/pki/external-ca", + "destination": "/documentation/platform/pki/ca/external-ca" + }, + { + "source": "/documentation/platform/pki/acme-ca", + "destination": "/documentation/platform/pki/ca/acme-ca" + }, + { + "source": "/documentation/platform/pki/azure-adcs", + "destination": "/documentation/platform/pki/ca/azure-adcs" + }, + { + "source": "/documentation/platform/pki/certificates", + "destination": "/documentation/platform/pki/certificates/certificates" + }, + { + "source": "/documentation/platform/pki/est", + "destination": "/documentation/platform/pki/enrollment-methods/est" } ] } diff --git a/docs/documentation/platform/gateways/gateway-deployment.mdx b/docs/documentation/platform/gateways/gateway-deployment.mdx index 9a5b7d8169..c7b8763c26 100644 --- a/docs/documentation/platform/gateways/gateway-deployment.mdx +++ b/docs/documentation/platform/gateways/gateway-deployment.mdx @@ -122,11 +122,13 @@ To successfully deploy an Infisical Gateway for use, follow these steps in order For production deployments on Linux servers, install the Gateway as a systemd service so that it runs securely in the background and automatically restarts on failure or system reboot: ```bash - sudo infisical gateway systemd install --token --domain --name --relay + sudo infisical gateway systemd install --token --domain --name sudo systemctl start infisical-gateway ``` - + + By default, the gateway connects to the most optimal relay. Use the `--relay` flag to manually specify a different relay server. + The systemd install command requires a Linux operating system with root/sudo @@ -153,10 +155,13 @@ To successfully deploy an Infisical Gateway for use, follow these steps in order --from-literal=INFISICAL_AUTH_METHOD=universal-auth \ --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_ID= \ --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET= \ - --from-literal=INFISICAL_RELAY_NAME= \ --from-literal=INFISICAL_GATEWAY_NAME= ``` + + By default, the gateway connects to the most optimal relay. Use the `--from-literal=INFISICAL_RELAY_NAME=` flag to manually specify a different relay server. + + #### Install the Gateway ```bash @@ -168,8 +173,12 @@ To successfully deploy an Infisical Gateway for use, follow these steps in order For development or testing environments: ```bash - infisical gateway start --token --relay= --name= + sudo infisical gateway start --token --name= ``` + + + By default, the gateway connects to the most optimal relay. Use the `--relay` flag to manually specify a different relay server. + diff --git a/docs/documentation/platform/pki/acme-ca.mdx b/docs/documentation/platform/pki/ca/acme-ca.mdx similarity index 98% rename from docs/documentation/platform/pki/acme-ca.mdx rename to docs/documentation/platform/pki/ca/acme-ca.mdx index 6ddbdc305c..76f5cdc8f5 100644 --- a/docs/documentation/platform/pki/acme-ca.mdx +++ b/docs/documentation/platform/pki/ca/acme-ca.mdx @@ -1,5 +1,5 @@ --- -title: "Certificates with ACME CA" +title: "ACME-compatible CA" description: "Learn how to automatically provision and manage TLS certificates using ACME Certificate Authorities like Let's Encrypt with Infisical PKI" --- @@ -257,6 +257,7 @@ In the following steps, we explore how to set up ACME Certificate Authority inte - Downloaded directly from the Infisical UI - Retrieved via the Infisical API for programmatic access using the [latest certificate bundle endpoint](/api-reference/endpoints/certificate-profiles/get-latest-active-bundle) + ## Example: Let's Encrypt Integration @@ -264,19 +265,23 @@ In the following steps, we explore how to set up ACME Certificate Authority inte Let's Encrypt is a free, automated, and open Certificate Authority that provides domain-validated SSL/TLS certificates. Here's how the integration works with Infisical: ### Production Environment + - **Directory URL**: `https://acme-v02.api.letsencrypt.org/directory` - **Rate Limits**: 50 certificates per registered domain per week - **Certificate Validity**: 90 days with automatic renewal - **Trusted By**: All major browsers and operating systems ### Staging Environment (for testing) + - **Directory URL**: `https://acme-staging-v02.api.letsencrypt.org/directory` - **Rate Limits**: Much higher limits for testing - **Certificate Validity**: 90 days (not trusted by browsers) - **Use Case**: Testing your ACME integration without hitting production rate limits - Always test your ACME integration using Let's Encrypt's staging environment first. This allows you to verify your DNS configuration and certificate issuance process without consuming your production rate limits. + Always test your ACME integration using Let's Encrypt's staging environment + first. This allows you to verify your DNS configuration and certificate + issuance process without consuming your production rate limits. ## Example: DigiCert Integration @@ -289,7 +294,9 @@ DigiCert is a leading commercial Certificate Authority providing a wide range of - **Trusted By**: All major browsers and operating systems. - When integrating with DigiCert ACME, ensure you have obtained the necessary External Account Binding (EAB) Key Identifier (KID) and HMAC Key from your DigiCert account. + When integrating with DigiCert ACME, ensure you have obtained the necessary + External Account Binding (EAB) Key Identifier (KID) and HMAC Key from your + DigiCert account. ## FAQ @@ -303,11 +310,13 @@ DigiCert is a leading commercial Certificate Authority providing a wide range of - Can be fully automated without manual intervention Support for additional DNS providers is planned for future releases. + Yes! ACME CAs like Let's Encrypt support wildcard certificates (e.g., `*.example.com`) when using DNS-01 validation. Simply specify the wildcard domain in your subscriber configuration. Note that wildcard certificates still require DNS-01 validation - HTTP-01 validation cannot be used for wildcard certificates. + Most ACME providers issue certificates with 90-day validity periods. This shorter validity period is designed to: @@ -317,6 +326,7 @@ DigiCert is a leading commercial Certificate Authority providing a wide range of - Ensure systems stay up-to-date with certificate management practices When configured, Infisical automatically handles certificate renewal for subscribers. + Yes! You can register multiple ACME CAs in the same project: @@ -326,5 +336,6 @@ DigiCert is a leading commercial Certificate Authority providing a wide range of - Backup providers for redundancy Each subscriber can be configured to use a specific ACME CA based on your requirements. + diff --git a/docs/documentation/platform/pki/azure-adcs.mdx b/docs/documentation/platform/pki/ca/azure-adcs.mdx similarity index 64% rename from docs/documentation/platform/pki/azure-adcs.mdx rename to docs/documentation/platform/pki/ca/azure-adcs.mdx index 23df3ed3b3..3bcb4b9122 100644 --- a/docs/documentation/platform/pki/azure-adcs.mdx +++ b/docs/documentation/platform/pki/ca/azure-adcs.mdx @@ -1,5 +1,5 @@ --- -title: "Certificates with Azure ADCS" +title: "Microsoft AD CS" description: "Learn how to issue and manage certificates using Microsoft Active Directory Certificate Services (ADCS) with Infisical." --- @@ -10,7 +10,7 @@ Issue and manage certificates using Microsoft Active Directory Certificate Servi Before setting up ADCS integration, ensure you have: - Microsoft Active Directory Certificate Services (ADCS) server running and accessible -- Domain administrator account with certificate management permissions +- Domain administrator account with certificate management permissions - ADCS web enrollment enabled on your server - Network connectivity from Infisical to the ADCS server - **IP whitelisting**: Your ADCS server must allow connections from Infisical's IP addresses @@ -24,67 +24,64 @@ This section walks you through the complete end-to-end process of setting up Azu - In your Infisical project, go to your **Certificate Project** → **Certificate Authority** to access the external CAs page. - - ![External CA Page](/images/platform/pki/azure-adcs/azure-adcs-external-ca-page.png) + In your Infisical project, go to your **Certificate Project** → + **Certificate Authority** to access the external CAs page. ![External CA + Page](/images/platform/pki/azure-adcs/azure-adcs-external-ca-page.png) - - - Click **Create CA** and configure: - - **Type**: Choose **Active Directory Certificate Services (AD CS)** - - **Name**: Friendly name for this CA (e.g., "Production ADCS CA") - - **App Connection**: Choose your ADCS connection from the dropdown - - ![External CA Form](/images/platform/pki/azure-adcs/azure-adcs-external-ca-form.png) - - - - Once created, your Azure ADCS Certificate Authority will appear in the list and be ready for use. - - ![External CA Created](/images/platform/pki/azure-adcs/azure-adcs-external-ca-created.png) - - - - Go to **Subscribers** to access the subscribers page. - - ![Subscribers Page](/images/platform/pki/azure-adcs/azure-adcs-subscribers-page.png) - - - - Click **Add Subscriber** and configure: - - **Name**: Unique subscriber name (e.g., "web-server-certs") - - **Certificate Authority**: Select your ADCS CA - - **Common Name**: Certificate CN (e.g., "api.example.com") - - **Certificate Template**: Select from dynamically loaded ADCS templates - - **Subject Alternative Names**: DNS names, IP addresses, or email addresses - - **TTL**: Certificate validity period (e.g., "1y" for 1 year) - - **Additional Subject Fields**: Organization, OU, locality, state, country, email (if required by template) - - ![Subscribers Form](/images/platform/pki/azure-adcs/azure-adcs-subscribers-form.png) - - - - Your subscriber is now created and ready to issue certificates. - - ![Subscriber Created](/images/platform/pki/azure-adcs/azure-adcs-subscribers-created.png) - - - - Click into your subscriber and click **Order Certificate** to generate a new certificate using your ADCS template. - - ![Issue New Certificate](/images/platform/pki/azure-adcs/azure-adcs-subscriber-issue-new-certificate.png) - - - - Your certificate has been successfully issued by the ADCS server and is ready for use. - - ![Certificate Created](/images/platform/pki/azure-adcs/azure-adcs-certificate-created.png) - - + + + Click **Create CA** and configure: - **Type**: Choose **Active Directory + Certificate Services (AD CS)** - **Name**: Friendly name for this CA (e.g., + "Production ADCS CA") - **App Connection**: Choose your ADCS connection from + the dropdown ![External CA + Form](/images/platform/pki/azure-adcs/azure-adcs-external-ca-form.png) + + + + Once created, your Azure ADCS Certificate Authority will appear in the list + and be ready for use. ![External CA + Created](/images/platform/pki/azure-adcs/azure-adcs-external-ca-created.png) + + + + Go to **Subscribers** to access the subscribers page. ![Subscribers + Page](/images/platform/pki/azure-adcs/azure-adcs-subscribers-page.png) + + + + Click **Add Subscriber** and configure: - **Name**: Unique subscriber name + (e.g., "web-server-certs") - **Certificate Authority**: Select your ADCS CA - + **Common Name**: Certificate CN (e.g., "api.example.com") - **Certificate + Template**: Select from dynamically loaded ADCS templates - **Subject + Alternative Names**: DNS names, IP addresses, or email addresses - **TTL**: + Certificate validity period (e.g., "1y" for 1 year) - **Additional Subject + Fields**: Organization, OU, locality, state, country, email (if required by + template) ![Subscribers + Form](/images/platform/pki/azure-adcs/azure-adcs-subscribers-form.png) + + + + Your subscriber is now created and ready to issue certificates. ![Subscriber + Created](/images/platform/pki/azure-adcs/azure-adcs-subscribers-created.png) + + + + Click into your subscriber and click **Order Certificate** to generate a new + certificate using your ADCS template. ![Issue New + Certificate](/images/platform/pki/azure-adcs/azure-adcs-subscriber-issue-new-certificate.png) + + + + Your certificate has been successfully issued by the ADCS server and is ready + for use. ![Certificate + Created](/images/platform/pki/azure-adcs/azure-adcs-certificate-created.png) + + - Navigate to **Certificates** to view detailed information about all issued certificates, including expiration dates, serial numbers, and certificate chains. - - ![Certificates Page](/images/platform/pki/azure-adcs/azure-adcs-certificates-page.png) + Navigate to **Certificates** to view detailed information about all issued + certificates, including expiration dates, serial numbers, and certificate + chains. ![Certificates + Page](/images/platform/pki/azure-adcs/azure-adcs-certificates-page.png) @@ -95,6 +92,7 @@ Infisical automatically retrieves available certificate templates from your ADCS ### Common Template Types ADCS templates you might see include: + - **Web Server**: For SSL/TLS certificates with server authentication - **Computer**: For machine authentication certificates - **User**: For client authentication certificates @@ -106,13 +104,16 @@ ADCS templates you might see include: ### Template Requirements Ensure your ADCS templates are configured with: + - **Enroll permissions** for your connection account - **Auto-enroll permissions** if using automated workflows - **Subject name requirements** matching your certificate requests - **Key usage extensions** appropriate for your use case -**Dynamic Template Discovery**: Infisical queries your ADCS server in real-time to populate available templates. Only templates you have permission to use will be displayed during certificate issuance. + **Dynamic Template Discovery**: Infisical queries your ADCS server in + real-time to populate available templates. Only templates you have permission + to use will be displayed during certificate issuance. ## Certificate Issuance Limitations @@ -120,10 +121,13 @@ Ensure your ADCS templates are configured with: ### Immediate Issuance Only -**Manual Approval Not Supported**: Infisical currently supports only **immediate certificate issuance**. Certificates that require manual approval or are held by ADCS policies cannot be issued through Infisical yet. + **Manual Approval Not Supported**: Infisical currently supports only + **immediate certificate issuance**. Certificates that require manual approval + or are held by ADCS policies cannot be issued through Infisical yet. For successful certificate issuance, ensure your ADCS templates and policies are configured to: + - **Auto-approve** certificate requests without manual intervention - **Not require** administrator approval for the templates you plan to use - **Allow** the connection account to request and receive certificates immediately @@ -131,19 +135,22 @@ For successful certificate issuance, ensure your ADCS templates and policies are ### What Happens with Manual Approval If a certificate request requires manual approval: + 1. The request will be submitted to ADCS successfully 2. Infisical will attempt to retrieve the certificate with exponential backoff (up to 5 retries over ~1 minute) 3. If the certificate is not approved within this timeframe, the request will **fail** 4. **No background polling**: Currently, Infisical does not check for certificates that might be approved hours or days later -**Future Enhancement**: Background polling for delayed certificate approvals is planned for future releases. + **Future Enhancement**: Background polling for delayed certificate approvals + is planned for future releases. ### Certificate Revocation -Certificate revocation is **not supported** by the Azure ADCS connector due to security and complexity considerations. + Certificate revocation is **not supported** by the Azure ADCS connector due to + security and complexity considerations. ## Advanced Configuration @@ -166,28 +173,33 @@ This allows Infisical to control certificate expiration dates directly. ### Common Issues **Certificate Request Denied** + - Verify ADCS template permissions for your connection account - Check template subject name requirements - Ensure template allows the requested key algorithm and size **Revocation Service Unavailable** + - Verify IIS is running and the revocation endpoint is accessible - Check IIS application pool permissions - Test endpoint connectivity from Infisical **Template Not Found** + - Verify template exists on ADCS server and is published - Check that your connection account has enrollment permissions for the template - Ensure the template is properly configured and available in the ADCS web enrollment interface - Templates are dynamically loaded - refresh the PKI Subscriber form if templates don't appear **Certificate Request Pending/Timeout** + - Check if your ADCS template requires manual approval - Infisical only supports immediate issuance - Verify the certificate template is configured for auto-approval - Ensure your connection account has sufficient permissions to request certificates without approval - Review ADCS server policies that might be holding the certificate request **Network Connectivity Issues** + - Verify your ADCS server's firewall allows connections from Infisical - For Infisical Cloud: Ensure Infisical's IP addresses are whitelisted (see [Networking Configuration](/documentation/setup/networking)) - For self-hosted: Whitelist your Infisical server's IP address on the ADCS server @@ -195,11 +207,13 @@ This allows Infisical to control certificate expiration dates directly. - Check for any network security appliances blocking the connection **Authentication Failures** + - Verify ADCS connection credentials - Check domain account permissions - Ensure network connectivity to ADCS server **SSL/TLS Certificate Errors** + - For ADCS servers with self-signed or private certificates: disable "Reject Unauthorized" in the SSL tab of your Azure ADCS app connection, or provide the certificate in PEM format - Common SSL errors: `UNABLE_TO_VERIFY_LEAF_SIGNATURE`, `SELF_SIGNED_CERT_IN_CHAIN`, `CERT_HAS_EXPIRED` - The SSL configuration applies to all HTTPS communications between Infisical and your ADCS server diff --git a/docs/documentation/platform/pki/ca/external-ca.mdx b/docs/documentation/platform/pki/ca/external-ca.mdx new file mode 100644 index 0000000000..1dc89ec96c --- /dev/null +++ b/docs/documentation/platform/pki/ca/external-ca.mdx @@ -0,0 +1,50 @@ +--- +title: "External CA" +sidebarTitle: "External CA" +description: "Learn how to connect External Certificate Authorities with Infisical." +--- + +## Concept + +Infisical lets you integrate with External Certificate Authorities (CAs), allowing you to use existing PKI infrastructure or connect to public CAs to issue digital certificates for your end-entities. + +
+ +```mermaid +graph TD + A1[External Public CA
e.g. Let's Encrypt, ZeroSSL, ...] --> Infisical + A2[External Private CA
e.g. AWS Private CA, HashiCorp Vault PKI, ...] --> Infisical +``` + +
+ +As shown above, these CAs commonly fall under two categories: + +- External Private CAs: CAs like AWS Private CA, HashiCorp Vault PKI, Azure ADCS, etc. that are privately owned and are used to issue certificates for internal services; these are often either cloud-hosted private CAs or on-prem / enterprise CAs. +- External Public CAs: CAs like Let's Encrypt, DigiCert, GlobalSign, etc. that are publicly trusted and are used to issue certificates for public-facing services. + +Note that Infisical can also act as an _ACME client_, allowing you to integrate upstream with any ACME-compatible CA to automate certificate issuance and renewal. + +## Workflow + +A typical workflow for integrating an External CA with Infisical consists of choosing the desired External CA type +and specifying the configuration or connection details necessary to connect to the CA. + +The specific steps and requirements vary depending on the External CA type you choose to integrate. + +## Supported External CA Types + +Infisical currently supports the following External CA types out of the box: + +- [ACME CA](/documentation/platform/pki/ca/acme-ca): An ACME-compatible CA that supports the ACME protocol, such as Let's Encrypt, ZeroSSL, Buypass, Digicert, etc. +- [Azure ADCS](/documentation/platform/pki/ca/azure-adcs): A Microsoft Active Directory Certificate Services (ADCS) that supports the ADCS protocol, such as AWS Private CA, Azure ADCS, etc. + +If you don’t see a specific external CA listed here or need a dedicated integration guide, please reach out to sales@infisical.com and we’ll help you set up the integration for your external CA. + +## FAQ + + + + Yes. You can have both Private and External CAs in the same project. + + diff --git a/docs/documentation/platform/pki/ca/overview.mdx b/docs/documentation/platform/pki/ca/overview.mdx new file mode 100644 index 0000000000..9a815992ae --- /dev/null +++ b/docs/documentation/platform/pki/ca/overview.mdx @@ -0,0 +1,13 @@ +--- +title: "Overview" +sidebarTitle: "Overview" +--- + +Before issuing and managing certificates with Infisical, you'll need to configure a Certificate Authority (CA). + +This is the trusted entity that signs and validates the X.509 certificates used to secure your end-entities. + +Infisical supports two categories of CAs: + +- [Internal CA](/documentation/platform/pki/ca/private-ca): Internally operated root and intermediate CAs managed within Infisical. This is useful if you need complete control over your PKI and are issuing certificates for private networks, internal services, or managed devices. +- [External CA](/documentation/platform/pki/ca/external-ca): Third-party public (e.g. Let's Encrypt, DigiCert) or private (e.g. AWS Private CA, HashiCorp Vault PKI, etc.) CAs that can be integrated with Infisical. This is useful if you want to leverage existing PKI infrastructure or issue publicly trusted certificates. diff --git a/docs/documentation/platform/pki/private-ca.mdx b/docs/documentation/platform/pki/ca/private-ca.mdx similarity index 92% rename from docs/documentation/platform/pki/private-ca.mdx rename to docs/documentation/platform/pki/ca/private-ca.mdx index 7d7ee12202..74913d4cc2 100644 --- a/docs/documentation/platform/pki/private-ca.mdx +++ b/docs/documentation/platform/pki/ca/private-ca.mdx @@ -1,13 +1,12 @@ --- -title: "Private CA" -sidebarTitle: "Private CA" +title: "Internal CA" +sidebarTitle: "Internal CA" description: "Learn how to create a Private CA hierarchy with Infisical." --- ## Concept -The first step to creating your Internal PKI is to create a Private Certificate Authority (CA) hierarchy that is a structure of entities -used to issue digital certificates for your [subscribers](/documentation/platform/pki/subscribers). +Infisical lets you build your Internal PKI through a Private Certificate Authority (CA) hierarchy, enabling you to issue and manage digital certificates for your end-entities.
@@ -47,7 +46,7 @@ consisting of an (optional) root CA and an intermediate CA. If you wish to use an external root CA, you can skip this step and head to step 2 to create an intermediate CA. - To create a root CA, head to your Project > Internal PKI > Certificate Authorities and press **Create CA**. + To create a root CA, head to your Certificate Management Project > Certificate Authorities > Internal Certificate Authorities and press **Create CA**. ![pki create ca](/images/platform/pki/ca/ca-create.png) @@ -55,18 +54,17 @@ consisting of an (optional) root CA and an intermediate CA. ![pki create root ca](/images/platform/pki/ca/ca-create-root.png) - Here's some guidance on each field: + Here's some guidance for each field: - Valid Until: The date until which the CA is valid in the date time string format specified [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format). For example, the following formats would be valid: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss.sssZ`. - Path Length: The maximum number of intermediate CAs that can be chained to this CA. A path of `-1` implies no limit; a path of `0` implies no intermediate CAs can be chained. - Key Algorithm: The type of public key algorithm and size, in bits, of the key pair that the CA creates when it issues a certificate. Supported key algorithms are `RSA 2048`, `RSA 4096`, `ECDSA P-256`, and `ECDSA P-384` with the default being `RSA 2048`. - - Friendly Name: A friendly name for the CA; this is only for display and defaults to the subject of the CA if left empty. + - Name: A slug-friendly name for the CA. - Organization (O): The organization name. - Country (C): The country code. - State or Province Name: The state or province. - Locality Name: The city or locality. - Common Name: The name of the CA. - - Require Template for Certificate Issuance: Whether or not certificates for this CA can only be issued through certificate templates (recommended). The Organization, Country, State or Province Name, Locality Name, and Common Name make up the **Distinguished Name (DN)** or **subject** of the CA. @@ -98,8 +96,7 @@ consisting of an (optional) root CA and an intermediate CA. ![pki cas](/images/platform/pki/ca/cas.png) - Great! You've successfully created a Private CA hierarchy with a root CA and an intermediate CA. - Now check out the [Subscribers](/documentation/platform/pki/subscribers) page to learn more about how to issue X.509 certificates using the intermediate CA. + Great! You've successfully created a Private CA hierarchy with a root CA and an intermediate CA. Now check out the [Certificates section](/documentation/platform/pki/certificates/overview) to learn more about how to issue X.509 certificates using the intermediate CA. 2.3b. If you have an external root CA, select **External CA** for the **Parent CA Type** field. @@ -110,7 +107,7 @@ consisting of an (optional) root CA and an intermediate CA. Finally, press **Install** to import the certificate and certificate chain as part of the installation step for the intermediate CA Great! You've successfully created a Private CA hierarchy with an intermediate CA chained to an external root CA. - Now check out the [Subscribers](/documentation/platform/pki/subscribers) page to learn more about how to issue X.509 certificates using the intermediate CA. + Now check out the [Certificates section](/documentation/platform/pki/certificates/overview) to learn more about how to issue X.509 certificates using the intermediate CA. diff --git a/docs/documentation/platform/pki/certificate-syncs/aws-certificate-manager.mdx b/docs/documentation/platform/pki/certificate-syncs/aws-certificate-manager.mdx index de064bf4e5..e33f46f3ee 100644 --- a/docs/documentation/platform/pki/certificate-syncs/aws-certificate-manager.mdx +++ b/docs/documentation/platform/pki/certificate-syncs/aws-certificate-manager.mdx @@ -5,82 +5,87 @@ description: "Learn how to configure an AWS Certificate Manager Certificate Sync **Prerequisites:** -- Set up and configure a [Certificate Authority](/documentation/platform/pki/overview) - Create an [AWS Connection](/integrations/app-connections/aws) The AWS Certificate Manager Certificate Sync requires the following ACM permissions to be set on the IAM user/role for Infisical to sync certificates to AWS Certificate Manager: `acm:ListCertificates`, `acm:DescribeCertificate`, `acm:ImportCertificate`, `acm:DeleteCertificate`, and `acm:ListTagsForCertificate`. - These permissions allow Infisical to list, import, tag, and manage certificates in your AWS Certificate Manager service. +These permissions allow Infisical to list, import, tag, and manage certificates in your AWS Certificate Manager service. + - Certificates synced to AWS Certificate Manager will be stored as imported certificates, preserving both the certificate and private key components. + Certificates synced to AWS Certificate Manager will be stored as imported + certificates, preserving both the certificate and private key components. - 1. Navigate to **Project** > **Integrations** and select the **Certificate Syncs** tab. Click on the **Add Sync** button. - ![Certificate Syncs Tab](/images/certificate-syncs/general/certificate-sync-tab.png) + 1. Navigate to **Project** > **Integrations** > **Certificate Syncs** and press **Add Sync**. + ![Certificate Syncs Tab](/images/platform/pki/certificate-syncs/general/create-certificate-sync.png) 2. Select the **AWS Certificate Manager** option. - ![Select ACM](/images/certificate-syncs/aws-certificate-manager/select-acm-option.png) + ![Select ACM](/images/platform/pki/certificate-syncs/aws-certificate-manager/select-acm-option.png) - 3. Configure the **Source** from where certificates should be retrieved, then click **Next**. - ![Configure Source](/images/certificate-syncs/aws-certificate-manager/acm-source.png) + 3. Configure the **Destination** to where certificates should be deployed, then click **Next**. + ![Configure Destination](/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-destination.png) - - **PKI Subscriber**: The PKI subscriber to retrieve certificates from. + - **AWS Connection**: The AWS Connection to authenticate with. + - **AWS Region**: The AWS region where certificates should be stored. - 4. Configure the **Destination** to where certificates should be deployed, then click **Next**. - ![Configure Destination](/images/certificate-syncs/aws-certificate-manager/acm-destination.png) + 4. Configure the **Sync Options** to specify how certificates should be synced, then click **Next**. + ![Configure Options](/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-options.png) - - **AWS Connection**: The AWS Connection to authenticate with. - - **AWS Region**: The AWS region where certificates should be stored. + - **Enable Removal of Expired/Revoked Certificates**: If enabled, Infisical will remove certificates from the destination if they are no longer active in Infisical. + - **Preserve ARN on Renewal**: If enabled, Infisical will sync renewed certificates to the destination under the same ARN as the original synced certificate instead of creating a new certificate with a new ARN. + - **Certificate Name Schema** (Optional): Customize how certificate tags are generated in AWS Certificate Manager. Must include `{{certificateId}}` as a placeholder for the certificate ID to ensure proper certificate identification and management. If not specified, defaults to `Infisical-{{certificateId}}`. + - **Auto-Sync Enabled**: If enabled, certificates will automatically be synced when changes occur. Disable to enforce manual syncing only. - 5. Configure the **Sync Options** to specify how certificates should be synced, then click **Next**. - ![Configure Options](/images/certificate-syncs/aws-certificate-manager/acm-options.png) - - - **Auto-Sync Enabled**: If enabled, certificates will automatically be synced from the source PKI subscriber when changes occur. Disable to enforce manual syncing only. - - **Enable Certificate Removal**: If enabled, Infisical will remove expired certificates from the destination during sync operations. Disable this option if you intend to manage certificate cleanup manually. - - **Certificate Name Schema** (Optional): Customize how certificate tags are generated in AWS Certificate Manager. Must include `{{certificateId}}` as a placeholder for the certificate ID to ensure proper certificate identification and management. If not specified, defaults to `Infisical-{{certificateId}}`. - - - **AWS Certificate Manager Certificate Limits**: AWS Certificate Manager has limits on the number of certificates per account and region. Refer to AWS documentation for current limits. Deleted certificates count toward your quota until they are permanently purged by AWS (typically after 30 days). - - - 6. Configure the **Details** of your AWS Certificate Manager Certificate Sync, then click **Next**. - ![Configure Details](/images/certificate-syncs/aws-certificate-manager/acm-details.png) + 5. Configure the **Details** of your AWS Certificate Manager Certificate Sync, then click **Next**. + ![Configure Details](/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-details.png) - **Name**: The name of your sync. Must be slug-friendly. - **Description**: An optional description for your sync. + 6. Select which certificates should be synced to AWS Certificate Manager. + ![Select Certificates](/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-certificates.png) + 7. Review your AWS Certificate Manager Certificate Sync configuration, then click **Create Sync**. - ![Confirm Configuration](/images/certificate-syncs/aws-certificate-manager/acm-review.png) + ![Confirm Configuration](/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-review.png) 8. If enabled, your AWS Certificate Manager Certificate Sync will begin syncing your certificates to the destination endpoint. - ![Sync Certificates](/images/certificate-syncs/aws-certificate-manager/acm-synced.png) - + ![Sync Certificates](/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-synced.png) To create an **AWS Certificate Manager Certificate Sync**, make an API request to the [Create AWS Certificate Manager Certificate Sync](/api-reference/endpoints/pki/syncs/aws-certificate-manager/create) API endpoint. ### Sample request + + You can optionally specify `certificateIds` during sync creation to immediately add certificates to the sync. + If not provided, you can add certificates later using the certificate management endpoints. + + ```bash Request curl --request POST \ --url https://app.infisical.com/api/v1/pki/syncs/aws-certificate-manager \ + --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "name": "my-acm-cert-sync", "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "description": "an example certificate sync", "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", - "subscriberId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "destination": "aws-certificate-manager", "isAutoSyncEnabled": true, + "certificateIds": [ + "550e8400-e29b-41d4-a716-446655440000", + "660f1234-e29b-41d4-a716-446655440001" + ], "syncOptions": { "canRemoveCertificates": true, + "preserveArnOnRenewal": true, "certificateNameSchema": "myapp-{{certificateId}}" }, "destinationConfig": { @@ -104,10 +109,10 @@ description: "Learn how to configure an AWS Certificate Manager Certificate Sync }, "syncOptions": { "canRemoveCertificates": true, + "preserveArnOnRenewal": true, "certificateNameSchema": "myapp-{{certificateId}}" }, "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", - "subscriberId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-01-01T00:00:00.000Z", "updatedAt": "2023-01-01T00:00:00.000Z" @@ -115,24 +120,27 @@ description: "Learn how to configure an AWS Certificate Manager Certificate Sync } ``` + ## Certificate Management Your AWS Certificate Manager Certificate Sync will: -- **Automatic Deployment**: Deploy new certificates issued by your PKI subscriber to AWS Certificate Manager -- **Certificate Updates**: Update certificates in AWS Certificate Manager when renewals occur -- **Expiration Handling**: Optionally remove expired certificates from AWS Certificate Manager (if enabled) +- **Automatic Deployment**: Deploy certificates in Infisical to AWS Certificate Manager. +- **Certificate Updates**: Update certificates in AWS Certificate Manager when renewals occur. +- **Expiration Handling**: Optionally remove expired certificates from AWS Certificate Manager (if enabled). - **Tagging**: Automatically tag certificates with an InfisicalCertificate tag for easy identification and management - AWS Certificate Manager Certificate Syncs support both automatic and manual synchronization modes. When auto-sync is enabled, certificates are automatically deployed as they are issued or renewed. + AWS Certificate Manager Certificate Syncs support both automatic and manual + synchronization modes. When auto-sync is enabled, certificates are + automatically deployed as they are issued or renewed. ## Manual Certificate Sync -You can manually trigger certificate synchronization from your PKI subscriber to AWS Certificate Manager using the sync certificates functionality. This is useful for: +You can manually trigger certificate synchronization to AWS Certificate Manager using the sync certificates functionality. This is useful for: - Initial setup when you have existing certificates to deploy - One-time sync of specific certificates @@ -142,5 +150,8 @@ You can manually trigger certificate synchronization from your PKI subscriber to To manually sync certificates, use the [Sync Certificates](/api-reference/endpoints/pki/syncs/aws-certificate-manager/sync-certificates) API endpoint or the manual sync option in the Infisical UI. -AWS Certificate Manager does not support importing certificates back into Infisical due to security limitations where private keys cannot be extracted from AWS Certificate Manager. Only certificates imported into ACM (not AWS-issued certificates) can be managed by the sync. - \ No newline at end of file + AWS Certificate Manager does not support importing certificates back into + Infisical due to security limitations where private keys cannot be extracted + from AWS Certificate Manager. Only certificates imported into ACM (not + AWS-issued certificates) can be managed by the sync. + diff --git a/docs/documentation/platform/pki/certificate-syncs/azure-key-vault.mdx b/docs/documentation/platform/pki/certificate-syncs/azure-key-vault.mdx index 6190ab3c3e..cfdbfe136c 100644 --- a/docs/documentation/platform/pki/certificate-syncs/azure-key-vault.mdx +++ b/docs/documentation/platform/pki/certificate-syncs/azure-key-vault.mdx @@ -5,47 +5,43 @@ description: "Learn how to configure an Azure Key Vault Certificate Sync for Inf **Prerequisites:** - - Set up and configure a [Certificate Authority](/documentation/platform/pki/overview) - - Create an [Azure Key Vault Connection](/integrations/app-connections/azure-key-vault) - - Ensure your network security policies allow incoming requests from Infisical to this certificate sync provider, if network restrictions apply. +- Create an [Azure Key Vault Connection](/integrations/app-connections/azure-key-vault) +- Ensure your network security policies allow incoming requests from Infisical to this certificate sync provider, if network restrictions apply. The Azure Key Vault Certificate Sync requires the following certificate permissions to be set on the user / service principal for Infisical to sync certificates to Azure Key Vault: `certificates/list`, `certificates/get`, `certificates/import`, `certificates/delete`. - Any role with these permissions would work such as the **Key Vault Certificates Officer** role. +Any role with these permissions would work such as the **Key Vault Certificates Officer** role. + - Certificates synced to Azure Key Vault will be stored as certificate objects, preserving both the certificate and private key components. + Certificates synced to Azure Key Vault will be stored as certificate objects, + preserving both the certificate and private key components. - 1. Navigate to **Project** > **Integrations** and select the **Certificate Syncs** tab. Click on the **Add Sync** button. - ![Certificate Syncs Tab](/images/certificate-syncs/general/certificate-sync-tab.png) + 1. Navigate to **Project** > **Integrations** > **Certificate Syncs** and press **Add Sync**. + ![Certificate Syncs Tab](/images/platform/pki/certificate-syncs/general/create-certificate-sync.png) 2. Select the **Azure Key Vault** option. - ![Select Key Vault](/images/certificate-syncs/azure-key-vault/select-key-vault-option.png) + ![Select Key Vault](/images/platform/pki/certificate-syncs/azure-key-vault/select-akv-option.png) - 3. Configure the **Source** from where certificates should be retrieved, then click **Next**. - ![Configure Source](/images/certificate-syncs/azure-key-vault/vault-source.png) - - - **PKI Subscriber**: The PKI subscriber to retrieve certificates from. - - 4. Configure the **Destination** to where certificates should be deployed, then click **Next**. - ![Configure Destination](/images/certificate-syncs/azure-key-vault/vault-destination.png) + 3. Configure the **Destination** to where certificates should be deployed, then click **Next**. + ![Configure Destination](/images/platform/pki/certificate-syncs/azure-key-vault/akv-destination.png) - **Azure Connection**: The Azure Connection to authenticate with. - **Vault Base URL**: The URL of your Azure Key Vault. -

- 5. Configure the **Sync Options** to specify how certificates should be synced, then click **Next**. - ![Configure Options](/images/certificate-syncs/azure-key-vault/vault-options.png) + 4. Configure the **Sync Options** to specify how certificates should be synced, then click **Next**. + ![Configure Options](/images/platform/pki/certificate-syncs/azure-key-vault/akv-options.png) - - **Auto-Sync Enabled**: If enabled, certificates will automatically be synced from the source PKI subscriber when changes occur. Disable to enforce manual syncing only. - - **Enable Certificate Removal**: If enabled, Infisical will remove expired certificates from the destination during sync operations. Disable this option if you intend to manage certificate cleanup manually. + - **Enable Removal of Expired/Revoked Certificates**: If enabled, Infisical will remove certificates from the destination if they are no longer active in Infisical. + - **Enable Versioning on Renewal**: If enabled, Infisical will sync renewed certificates to the destination under a new version of the original synced certificate instead of creating a new certificate. - **Certificate Name Schema** (Optional): Customize how certificate names are generated in Azure Key Vault. Use `{{certificateId}}` as a placeholder for the certificate ID. If not specified, defaults to `Infisical-{{certificateId}}`. + - **Auto-Sync Enabled**: If enabled, certificates will automatically be synced when changes occur. Disable to enforce manual syncing only. **Azure Key Vault Soft Delete**: When certificates are removed from Azure Key Vault, they are placed in a soft-deleted state rather than being permanently deleted. This means: @@ -53,38 +49,50 @@ description: "Learn how to configure an Azure Key Vault Certificate Sync for Inf - To resync removed certificates, you must either manually **purge** them from Azure Key Vault or **recover** them through the Azure portal/CLI - 6. Configure the **Details** of your Azure Key Vault Certificate Sync, then click **Next**. - ![Configure Details](/images/certificate-syncs/azure-key-vault/vault-details.png) + 5. Configure the **Details** of your Azure Key Vault Certificate Sync, then click **Next**. + ![Configure Details](/images/platform/pki/certificate-syncs/azure-key-vault/akv-details.png) - **Name**: The name of your sync. Must be slug-friendly. - **Description**: An optional description for your sync. + 6. Select which certificates should be synced to Azure Key Vault. + ![Select Certificates](/images/platform/pki/certificate-syncs/azure-key-vault/akv-certificates.png) + 7. Review your Azure Key Vault Certificate Sync configuration, then click **Create Sync**. - ![Confirm Configuration](/images/certificate-syncs/azure-key-vault/vault-review.png) + ![Confirm Configuration](/images/platform/pki/certificate-syncs/azure-key-vault/akv-review.png) 8. If enabled, your Azure Key Vault Certificate Sync will begin syncing your certificates to the destination endpoint. - ![Sync Certificates](/images/certificate-syncs/azure-key-vault/vault-synced.png) - + ![Sync Certificates](/images/platform/pki/certificate-syncs/azure-key-vault/akv-synced.png) To create an **Azure Key Vault Certificate Sync**, make an API request to the [Create Azure Key Vault Certificate Sync](/api-reference/endpoints/pki/syncs/azure-key-vault/create) API endpoint. ### Sample request + + You can optionally specify `certificateIds` during sync creation to immediately add certificates to the sync. + If not provided, you can add certificates later using the certificate management endpoints. + + ```bash Request curl --request POST \ --url https://app.infisical.com/api/v1/pki/syncs/azure-key-vault \ + --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "name": "my-key-vault-cert-sync", "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "description": "an example certificate sync", "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", - "subscriberId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "destination": "azure-key-vault", "isAutoSyncEnabled": true, + "certificateIds": [ + "550e8400-e29b-41d4-a716-446655440000", + "660f1234-e29b-41d4-a716-446655440001" + ], "syncOptions": { "canRemoveCertificates": true, + "enableVersioningOnRenewal": true, "certificateNameSchema": "myapp-{{certificateId}}" }, "destinationConfig": { @@ -108,10 +116,10 @@ description: "Learn how to configure an Azure Key Vault Certificate Sync for Inf }, "syncOptions": { "canRemoveCertificates": true, + "enableVersioningOnRenewal": true, "certificateNameSchema": "myapp-{{certificateId}}" }, "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", - "subscriberId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "createdAt": "2023-01-01T00:00:00.000Z", "updatedAt": "2023-01-01T00:00:00.000Z" @@ -119,24 +127,27 @@ description: "Learn how to configure an Azure Key Vault Certificate Sync for Inf } ``` + ## Certificate Management Your Azure Key Vault Certificate Sync will: -- **Automatic Deployment**: Deploy new certificates issued by your PKI subscriber to Azure Key Vault +- **Automatic Deployment**: Deploy certificates in Infisical to Azure Key Vault. - **Certificate Updates**: Update certificates in Azure Key Vault when renewals occur - **Expiration Handling**: Optionally remove expired certificates from Azure Key Vault (if enabled) - **Format Preservation**: Maintain certificate format and metadata during sync operations - Azure Key Vault Certificate Syncs support both automatic and manual synchronization modes. When auto-sync is enabled, certificates are automatically deployed as they are issued or renewed. + Azure Key Vault Certificate Syncs support both automatic and manual + synchronization modes. When auto-sync is enabled, certificates are + automatically deployed as they are issued or renewed. ## Manual Certificate Sync -You can manually trigger certificate synchronization from your PKI subscriber to Azure Key Vault using the sync certificates functionality. This is useful for: +You can manually trigger certificate synchronization to Azure Key Vault using the sync certificates functionality. This is useful for: - Initial setup when you have existing certificates to deploy - One-time sync of specific certificates @@ -146,5 +157,7 @@ You can manually trigger certificate synchronization from your PKI subscriber to To manually sync certificates, use the [Sync Certificates](/api-reference/endpoints/pki/syncs/azure-key-vault/sync-certificates) API endpoint or the manual sync option in the Infisical UI. -Azure Key Vault does not support importing certificates back into Infisical due to security limitations where private keys cannot be extracted from Azure Key Vault. - \ No newline at end of file + Azure Key Vault does not support importing certificates back into Infisical + due to security limitations where private keys cannot be extracted from Azure + Key Vault. + diff --git a/docs/documentation/platform/pki/certificate-syncs/overview.mdx b/docs/documentation/platform/pki/certificate-syncs/overview.mdx index db4844a922..d7931d8936 100644 --- a/docs/documentation/platform/pki/certificate-syncs/overview.mdx +++ b/docs/documentation/platform/pki/certificate-syncs/overview.mdx @@ -3,17 +3,19 @@ sidebarTitle: "Overview" description: "Learn how to sync certificates from Infisical PKI to third-party services." --- -Certificate Syncs enable you to sync certificates from Infisical PKI to third-party services using [App Connections](/integrations/app-connections/overview). +Certificate Syncs enable you to push certificates from Infisical to third-party services using [App Connections](/integrations/app-connections/overview). - Certificate Syncs are designed to automatically deploy certificates issued by your Certificate Authority to external services, ensuring your certificates are always up-to-date across your infrastructure. + Certificate Syncs are designed to automatically deploy certificates issued by + your Certificate Authority to external services, ensuring your certificates + are always up-to-date across your infrastructure. ## Concept -Certificate Syncs are a project-level resource used to sync certificates, via an [App Connection](/integrations/app-connections/overview), from a particular PKI subscriber (source) -to a third-party service (destination). When new certificates are issued or existing certificates are renewed, changes will automatically be propagated to the destination, ensuring -your certificates are always current. +Certificate Syncs are a project-level resource used to push certificates, via an [App Connection](/integrations/app-connections/overview), from Infisical +to a third-party service (destination). When paired with [server-side auto-renewal](/documentation/platform/pki/certificates/certificates#server-driven-certificate-renewal), renewed certificates are automatically synced to the destination, +ensuring your certificates stay current.
@@ -31,17 +33,15 @@ your certificates are always current. G[Certificate 1] H[Certificate 2] I[Certificate 3] - J[PKI Subscriber] B --> A - C --> J - D --> J - E --> J + C --> B + D --> B + E --> B A --> F F --> G F --> H F --> I - J --> B classDef default fill:#ffffff,stroke:#666,stroke-width:2px,rx:10px,color:black classDef connection fill:#FFF2B2,stroke:#E6C34A,stroke-width:2px,color:black,rx:15px @@ -61,39 +61,50 @@ your certificates are always current. ## Workflow -Configuring a Certificate Sync requires three components: a source PKI subscriber to retrieve certificates from, +Configuring a Certificate Sync requires three components: The certificates that you'd like to push, a destination endpoint to deploy certificates to, and configuration options to determine how your certificates should be synced. Follow these steps to start syncing: - For step-by-step guides on syncing to a particular third-party service, refer to the Certificate Syncs section in the Navigation Bar. + For step-by-step guides on syncing to a particular third-party service, refer + to the Certificate Syncs section in the Navigation Bar. -1. Create App Connection: If you have not already done so, create an [App Connection](/integrations/app-connections/overview) -via the UI or API for the third-party service you intend to sync certificates to. +1. Create App Connection: If you have not already done so, create + an [App Connection](/integrations/app-connections/overview) via the UI or API + for the third-party service you intend to sync certificates to. -2. Create Certificate Sync: Configure a Certificate Sync in the desired project by specifying the following parameters via the UI or API: - - Source: The PKI subscriber you wish to retrieve certificates from. - - Destination: The App Connection to utilize and the destination endpoint to deploy certificates to. These can vary between services. - - Options: Customize how certificates should be synced, including: - - Whether certificates should be removed from the destination when they expire - - Certificate naming schema to control how certificate names are generated in the destination +2. Create Certificate Sync: Configure a Certificate Sync in the + desired project by specifying the following parameters via the UI or API: + + - Destination: The App Connection to utilize and the destination + endpoint to deploy certificates to such as [AWS Certificate Manager](/documentation/platform/pki/certificate-syncs/aws-certificate-manager) + or [Azure Key Vault](/documentation/platform/pki/certificate-syncs/azure-key-vault). + - Certificates: The certificates you wish to push to the destination. + - Options: Customize how certificates should be synced, including: + - Whether certificates should be removed from the destination when they expire. + - Certificate naming schema to control how certificate names are generated in + the destination. - Only certificates managed by Infisical will be affected during sync operations. Certificates not created or - managed by Infisical will remain untouched, and changes made to Infisical-managed certificates directly - in the destination service may be overwritten by future syncs. + Only certificates managed by Infisical will be affected during sync + operations. Certificates not created or managed by Infisical will remain + untouched, and changes made to Infisical-managed certificates directly in the + destination service may be overwritten by future syncs. - Some third-party services do not support removing expired certificates automatically. + Some third-party services do not support removing expired certificates + automatically. -3. Utilize Sync: Any new certificates issued or renewals from the source PKI subscriber will now automatically be propagated to the destination endpoint. +3. Utilize Sync: Selected certificates will now be pushed to the + destination endpoint and automatically redeployed whenever they are renewed. - Infisical is continuously expanding its Certificate Sync third-party service support. If the service you need isn't available, - contact us at team@infisical.com to make a request. + Infisical is continuously expanding its Certificate Sync third-party service + support. If the service you need isn't available, contact us at + team@infisical.com to make a request. ## Certificate Naming @@ -111,32 +122,12 @@ You can customize certificate naming by providing a **Certificate Name Schema** - `{{certificateId}}` - The unique certificate identifier (required) **Examples:** + - `myapp-{{certificateId}}` → `myapp-abc123def456` - `ssl/{{certificateId}}` → `ssl/abc123def456` **Rules:** + - Must include exactly one `{{certificateId}}` placeholder -- Only alphanumeric characters, dashes (-), underscores (_), and slashes (/) are allowed +- Only alphanumeric characters, dashes (-), underscores (\_), and slashes (/) are allowed - Certificate names matching your schema will be managed by Infisical during sync operations - -## Certificate Management - -Certificate Syncs handle the full lifecycle of certificate management: - -- **Automatic Deployment**: New certificates are automatically deployed to configured destinations -- **Renewal Propagation**: Certificate renewals are seamlessly pushed to all connected services -- **Expiration Handling**: Expired certificates can be automatically removed from destinations (service-dependent) -- **Certificate Validation**: Certificates are validated before deployment to ensure integrity - -

- ```mermaid - graph LR - A[Certificate Issued] -->|Deploy| B[Destination Service] - C[Certificate Renewed] -->|Update| B - D[Certificate Expired] -->|Remove| B - style B fill:#F4FFE6,stroke:#96D600,stroke-width:2px,color:black,rx:15px - style A fill:#E6F4FF,stroke:#0096D6,stroke-width:2px,color:black,rx:15px - style C fill:#E6F4FF,stroke:#0096D6,stroke-width:2px,color:black,rx:15px - style D fill:#FFE6E6,stroke:#D63F3F,stroke-width:2px,color:black,rx:15px - ``` -
\ No newline at end of file diff --git a/docs/documentation/platform/pki/certificates/certificates.mdx b/docs/documentation/platform/pki/certificates/certificates.mdx new file mode 100644 index 0000000000..b30a53091e --- /dev/null +++ b/docs/documentation/platform/pki/certificates/certificates.mdx @@ -0,0 +1,164 @@ +--- +title: "Certificates" +sidebarTitle: "Certificates" +--- + + + PKI architecture is a complex topic and there are many ways to orchestrate + certificate management including renewal operations. For specific guidance and + access to enterprise features, we recommend reaching out to + sales@infisical.com to schedule a demo. + + +## Concept + +A certificate is the (X.509) leaf certificate issued for a certificate profile. + +Once issued, a certificate is kept track of in the certificate inventory +where you can manage various aspects of its lifecycle including deployment to cloud key stores, server-side auto-renewal behavior, revocation, and more. + +## Guide to Issuing Certificates + +To issue a certificate, you must first create a [certificate profile](/documentation/platform/pki/certificates/profiles) and a [certificate template](/documentation/platform/pki/certificates/templates) to go along with it. + +The [enrollment method](/documentation/platform/pki/enrollment-methods/overview) configured on the certificate profile determines how a certificate is issued for it. +Refer to the documentation for each enrollment method below to learn more about how to issue certificates using it. + +- [API](/documentation/platform/pki/enrollment-methods/api): Issue a certificate over UI or by making an API request to Infisical. +- [EST](/documentation/platform/pki/enrollment-methods/est): Issue a certificate over the EST protocol. + +## Guide to Renewing Certificates + +To [renew a certificate](/documentation/platform/pki/concepts/certificate-lifecycle#renewal), you can either request a new certificate from a certificate profile or have the platform +automatically request a new one for you. Whether you pursue a client-driven or server-driven approach is totally dependent on the enrollment method configured on your certificate +profile as well as your infrastructure use-case. + +### Client-Driven Certificate Renewal + +Client-driven certificate renewal is when renewal is initiated client-side by the end-entity consuming the certificate. +This is the most common approach to certificate renewal and is suitable for most use-cases. + +### Server-Driven Certificate Renewal + +Server-driven certificate renewal is when renewal is initiated server-side by Infisical rather than by the end-entity consuming the certificate. +When a certificate considered for auto-renewal meets a specified _renewal days before expiration_ threshold, Infisical reaches out to the issuing CA bound to the [certificate profile](/documentation/platform/pki/certificates/profiles) of the expiring certificate +to request for a new one. +The resulting renewed certificate is stored in the platform and made available to be fetched back or pushed downstream to end-entities or external systems such as cloud key stores. + +Note that server-driven certificate renewal is only available for certificates issued via the [API enrollment method](/documentation/platform/pki/enrollment-methods/api) where key pairs are generated server-side. +A certificate can be considered for auto-renewal at time of issuance if the **Enable Auto-Renewal By Default** option is selected on its [certificate profile](/documentation/platform/pki/certificates/profiles) or after issuance by toggling this option manually. + + + For server-driven certificate renewal workflows, you can programmatically fetch the latest active certificate bundle for a certificate profile using the [Get Latest Active Certificate Bundle](/api-reference/endpoints/certificate-profiles/get-latest-active-bundle) API endpoint. + + This ensures you always retrieve the most current valid certificate, including any that have been automatically renewed, making it particularly useful for deployment pipelines and automation workflows where you don't want to track individual serial numbers. + + +The following examples demonstrate different approaches to certificate renewal: + +- Using the ACME enrollment method, you may connect an ACME client like [certbot](https://certbot.eff.org/) to fetch back and renew certificates for Apache, Nginx, or other server. The ACME client will pursue a client-driven approach and submit certificate requests upon certificate expiration for you, saving renewed certificates back to the server's configuration. +- Using the ACME enrollment method, you may use [cert-manager](https://cert-manager.io/) with Infisical to issue and renew certificates for Kubernetes workloads; cert-manager will pursue a client-driven approach and submit certificate requests upon certificate expiration for you, saving renewed certificates back to Kubernetes secrets. +- Using the API enrollment method, you may push and auto-renew certificates to AWS and Azure using [certificate syncs](/documentation/platform/pki/certificate-syncs/overview). Certificates issued over the API enrollment method, where key pairs are generated server-side, are also eligible for server-side auto-renewal; once renewed, certificates are automatically pushed back to their sync destination. + +## Guide to Revoking Certificates + +In the following steps, we explore how to revoke a X.509 certificate and obtain a Certificate Revocation List (CRL) for a CA. + + + + + + Assuming that you've issued a certificate under a CA, you can revoke it by + selecting the **Revoke Certificate** option for it and specifying the reason + for revocation. + + ![pki revoke certificate](/images/platform/pki/certificate/cert-revoke.png) + + ![pki revoke certificate modal](/images/platform/pki/certificate/cert-revoke-modal.png) + + + + In order to check the revocation status of a certificate, you can check it + against the CRL of a CA by heading to its Issuing CA and downloading the CRL. + + ![pki view crl](/images/platform/pki/ca/ca-crl.png) + + To verify a certificate against the + downloaded CRL with OpenSSL, you can use the following command: + +```bash +openssl verify -crl_check -CAfile chain.pem -CRLfile crl.pem cert.pem +``` + +Note that you can also obtain the CRL from the certificate itself by +referencing the CRL distribution point extension on the certificate. + +To check a certificate against the CRL distribution point specified within it with OpenSSL, you can use the following command: + +```bash +openssl verify -verbose -crl_check -crl_download -CAfile chain.pem cert.pem +``` + + + + + + + + Assuming that you've issued a certificate under a CA, you can revoke it by making an API request to the [Revoke Certificate](/api-reference/endpoints/certificate-authorities/revoke) API endpoint, + specifying the serial number of the certificate and the reason for revocation. + + ### Sample request + + ```bash Request + curl --location --request POST 'https://app.infisical.com/api/v1/pki/certificates//revoke' \ + --header 'Authorization: Bearer ' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "revocationReason": "UNSPECIFIED" + }' + ``` + + ### Sample response + + ```bash Response + { + message: "Successfully revoked certificate", + serialNumber: "...", + revokedAt: "..." + } + ``` + + + In order to check the revocation status of a certificate, you can check it against the CRL of the issuing CA. + To obtain the CRLs of the CA, make an API request to the [List CRLs](/api-reference/endpoints/certificate-authorities/crls) API endpoint. + + ### Sample request + + ```bash Request + curl --location --request GET 'https://app.infisical.com/api/v1/pki/ca//crls' \ + --header 'Authorization: Bearer ' + ``` + + ### Sample response + + ```bash Response + [ + { + id: "...", + crl: "..." + }, + ... + ] + ``` + + To verify a certificate against the CRL with OpenSSL, you can use the following command: + + ```bash + openssl verify -crl_check -CAfile chain.pem -CRLfile crl.pem cert.pem + ``` + + + + + diff --git a/docs/documentation/platform/pki/certificates/overview.mdx b/docs/documentation/platform/pki/certificates/overview.mdx new file mode 100644 index 0000000000..a4688388a1 --- /dev/null +++ b/docs/documentation/platform/pki/certificates/overview.mdx @@ -0,0 +1,15 @@ +--- +title: "Overview" +sidebarTitle: "Overview" +--- + +To issue a certificate with Infisical, you create a certificate profile and a certificate template to go along with it. You then issue a certificate against +a specific profile depending on the enrollment method associated with it. + +There are three components to understand: + +- [Certificate Profile](/documentation/platform/pki/certificates/profiles): A configuration set specifying how certificates should be issued under that profile including the [issuing CA](/documentation/platform/pki/ca/overview), a certificate template, and the [enrollment method](/documentation/platform/pki/enrollment-methods/overview) (such as ACME, EST, API, etc.) used to enroll certificates. + +- [Certificate Template](/documentation/platform/pki/certificates/templates): A policy structure specifying the permitted attributes for requested certificates including subject naming conventions, SAN fields, key usages, and extended key usages. + +- [Certificate](/documentation/platform/pki/certificates/certificate): The actual X.509 certificate issued for a profile. Once issued, a certificate kept track of in the certificate inventory. diff --git a/docs/documentation/platform/pki/certificates/profiles.mdx b/docs/documentation/platform/pki/certificates/profiles.mdx new file mode 100644 index 0000000000..ccbef89cd1 --- /dev/null +++ b/docs/documentation/platform/pki/certificates/profiles.mdx @@ -0,0 +1,28 @@ +--- +title: "Certificate Profiles" +sidebarTitle: "Profiles" +--- + +## Concept + +A certificate profile is a configuration set specifying how leaf certificates should be issued for a group of end-entities including the [issuing CA](/documentation/platform/pki/ca/overview), a [certificate template](/documentation/platform/pki/certificates/templates), and the [enrollment method](/documentation/platform/pki/enrollment-methods/overview) (e.g. ACME, EST, API, etc.) used to enroll certificates. + +You typically request certificates against a certificate profile through its associated enrollment method. Each method defines its own interaction flow which you can read more about in its respective documentation. + +## Guide to Creating a Certificate Profile + +To create a certificate profile, head to your Certificate Management Project > Certificates > Certificate Profiles and press **Create Profile**. + +![pki certificate profile](/images/platform/pki/certificate/cert-profile.png) + +![pki certificate profile modal](/images/platform/pki/certificate/cert-profile-modal.png) + +Here's some guidance on each field: + +- Name: A slug-friendly name for the profile such as `web-servers`. +- Description: An optional description for the profile. +- Issuing CA: The [issuing CA](/documentation/platform/pki/ca/overview) that should be used to issue certificates for the profile. +- Certificate Template: The [certificate template](/documentation/platform/pki/certificates/templates) that should be used to validate certificate requests for the profile. +- Enrollment Method: The enrollment method that should be used to enroll certificates for the profile such as ACME, EST, API, etc. + +Depending on which enrollment method you choose, you may be presented with additional enrollment-specific configuration fields. diff --git a/docs/documentation/platform/pki/certificates/templates.mdx b/docs/documentation/platform/pki/certificates/templates.mdx new file mode 100644 index 0000000000..38b5570ddc --- /dev/null +++ b/docs/documentation/platform/pki/certificates/templates.mdx @@ -0,0 +1,30 @@ +--- +title: "Certificate Templates" +sidebarTitle: "Templates" +--- + +## Concept + +A certificate template is a policy structure specifying permitted attributes for requested certificates. This includes constraints around subject naming conventions, SAN fields, key usages, and extended key usages. + +Each certificate requested against a profile is validated against the template bound to that profile. If the request fails any criteria included in the template, the certificate is not issued. This helps administrators enforce uniformity and security standards across all issued certificates. + +## Guide to Creating a Certificate Template + +To create a certificate template, head to your Certificate Management Project > Certificates > Certificate Templates and press **Create Template**. + +![pki certificate template](/images/platform/pki/certificate/cert-template.png) + +![pki certificate template modal](/images/platform/pki/certificate/cert-template-modal.png) + +Here's some guidance on each field: + +- Template Name: A slug-friendly name for the template such as `tls-server`. +- Description: An optional description for the template. +- Subject Attributes: A list of common names that can be included in the certificate subject. Each row accepts a fixed value or pattern such as `example.com` or `*.example.com` and whether it is allowed or denied. +- Subject Alternative Names (SANs): A list of SANs that can appear in the certificate. Each row accepts a SAN type (e.g. DNS, IP, Email, URI), a fixed value or pattern such as `example.com` or `*.example.com`, and an allow or deny flag. +- Allowed Signature Algorithms: The set of signature algorithms permitted to sign certificates under this template such as `SHA256-RSA`, `SHA512-RSA`, etc. +- Allowed Key Algorithms: The set of public key algorithms permitted for certificate requests such as `RSA-2048`, `RSA-4096`, etc. +- Key Usages: The cryptographic purposes of the certificate such as Digital Signature, Key Encipherment, etc. +- Extended Key Usages: The higher-level intended uses of the certificate such as Server Authentication, Client Authentication, etc. +- Certificate Validity: The maximum lifetime of certificates that can be requested for certificates validated against this template. You can specify both a duration and unit (days, months, or years). diff --git a/docs/documentation/platform/pki/concepts/certificate-lifecycle.mdx b/docs/documentation/platform/pki/concepts/certificate-lifecycle.mdx new file mode 100644 index 0000000000..cf19ac8670 --- /dev/null +++ b/docs/documentation/platform/pki/concepts/certificate-lifecycle.mdx @@ -0,0 +1,53 @@ +--- +title: "Certificate Lifecycle" +description: "Learn what is the certificate lifecycle and how it works." +--- + +## Certificate Lifecycle + +Typically, a certificate goes through a series of stages during its lifetime from creation to retirement. This is called the certificate lifecycle. The exact names of these stages may vary from vendor to vendor, but they typically include [discovery](/documentation/platform/pki/concepts/certificate-lifecycle#discovery), [enrollment](/documentation/platform/pki/concepts/certificate-lifecycle#enrollment), [deployment](/documentation/platform/pki/concepts/certificate-lifecycle#deployment), [renewal](/documentation/platform/pki/concepts/certificate-lifecycle#renewal), [revocation](/documentation/platform/pki/concepts/certificate-lifecycle#revocation), and [retirement](/documentation/platform/pki/concepts/certificate-lifecycle#retirement). + +Note that not every stage is needed. For instance: + +- You are not required to discover certificates in order to start issuing and managing them. +- You may not need to revoke a certificate explicitly if it expires naturally and is replaced during routine renewal. + +## Discovery + +Certificate discovery is the process of identifying all active and inactive certificates across an environment, including those found on web servers, load balancers, services, and devices. A complete inventory prevents outages from forgotten certificates and creates the foundation for automation and monitoring. + +## Enrollment (Request / Issuance) + +Certificate enrollment is the process of requesting a certificate from a CA and can follow different approaches depending on the system or protocol in use. + +Common approaches to certificate enrollment include: + +- CSR-based enrollment: The client generates a key pair locally and submits a Certificate Signing Request (CSR) to a CA for certificate issuance. +- CSR-less enrollment: The client requests a certificate directly from a CA which may handle key generation internally and return the key pair in the response. + +Enrollment can be manually completed via API or fully automated using protocols like EST or ACME. The choice of enrollment method depends on security requirements, operational constraints, and integration context. + +## Deployment + +Certificate deployment involves installing the issued certificate on the appropriate systems and services, such as web servers, load balancers, or internal endpoints. It can also include distributing or [synchronizing certificates](/documentation/platform/pki/certificate-syncs/overview) to external systems like cloud key stores (e.g., AWS Secrets Manager, Google Secret Manager, Azure Key Vault) so they can be securely consumed by workloads running in the cloud. + +Deployment can happen manually or through automated mechanisms such as configuration pipelines, agents, or webhook integrations. + +## Renewal + +Certificate renewal is the process of requesting a new certificate from a CA before it expires to maintain trust and availability; this process can involve reusing the same key pair or rotating to a new one. + +The renewal process can be server-driven or client-driven: + +- Server-driven: Infisical automatically renews the certificate on your behalf. The renewed certificate is stored in the platform and can be synchronized to external systems such as cloud key stores. +- Client-driven: An external client, such as an agent or workload, initiates the renewal against Infisical. This is useful when key material needs to remain under client control or when rotation is tied to application-specific logic. + +This flexibility allows certificates to be renewed in a way that aligns with different security, automation, and infrastructure models. + +## Revocation + +Certificate revocation is the process of invalidating a certificate to prevent it from being used. This is required when a certificate is compromised, misconfigured, or no longer needed. The CA signals this status to clients through CRLs or OCSP. A new certificate can be issued and deployed if needed. + +## Retirement + +Certificate retirement is the process of removing a certificate from the system. This is typically done when a certificate is no longer needed or has expired. diff --git a/docs/documentation/platform/pki/concepts/certificate-mgmt.mdx b/docs/documentation/platform/pki/concepts/certificate-mgmt.mdx new file mode 100644 index 0000000000..efdb8a072c --- /dev/null +++ b/docs/documentation/platform/pki/concepts/certificate-mgmt.mdx @@ -0,0 +1,20 @@ +--- +title: "Certificate Management" +description: "Learn what is certificate management and why it matters for building secure systems." +--- + +## What is a Certificate? + +A (digital) _certificate_ is a file that is tied to a cryptographic key pair and is used to verify the identity of a website, user, device, or service. It helps establish trust and secure, encrypted communication between systems. + +For example, when you visit a website over HTTPS, your browser checks the TLS certificate deployed on the web server or load balancer to make sure it’s really the site it claims to be. If the certificate is valid, your browser establishes an encrypted connection with the server. + +Certificates contain information about the subject (who it identifies), the public key, and a digital signature from the CA that issued the certificate. They also include additional fields such as key usages, validity periods, and extensions that define how and where the certificate can be used. When a certificate expires, the service presenting it is no longer trusted, and clients won't be able to establish a secure connection to the service. + +## What is Certificate Management? + +As infrastructure scales and systems become more distributed, certificates sprawl. Without proper visibility and automation in place, certificates scatter across IT infrastructure, creating blind spots that can lead to service outages when certificates aren't renewed in time. + +To solve certificate sprawl and avoid outages, organizations rely on certificate management: the practice of centralizing and automating the certificate lifecycle from issuance through renewal and revocation. + +A consistent approach makes it easier to keep certificates valid and trusted, reduce operational risk, and maintain secure communication across environments. diff --git a/docs/documentation/platform/pki/enrollment-methods/acme.mdx b/docs/documentation/platform/pki/enrollment-methods/acme.mdx new file mode 100644 index 0000000000..559b0cab81 --- /dev/null +++ b/docs/documentation/platform/pki/enrollment-methods/acme.mdx @@ -0,0 +1,8 @@ +--- +title: "Certificate Enrollment via ACME" +sidebarTitle: "ACME" +--- + + + ACME-based certificate enrollment is currently under development and will be included in a future release. + diff --git a/docs/documentation/platform/pki/enrollment-methods/api.mdx b/docs/documentation/platform/pki/enrollment-methods/api.mdx new file mode 100644 index 0000000000..dac7b63863 --- /dev/null +++ b/docs/documentation/platform/pki/enrollment-methods/api.mdx @@ -0,0 +1,179 @@ +--- +title: "Certificate Enrollment via API" +sidebarTitle: "API" +--- + +## Concept + +The API enrollment method allows you to issue certificates against a specific certificate profile over Web UI or by making an API request to Infisical. + +## Guide to Certificate Enrollment via API + +In the following steps, we explore how to issue a X.509 certificate using the API enrollment method. + + + + + + + Create a [certificate + profile](/documentation/platform/pki/certificates/profiles) with **API** + selected as the enrollment method. + + Notice that the API enrollment method supports an option called **Enable Auto-Renewal By Default**. + If selected, _eligible_ certificates are automatically considered for server-side auto-renewal based + on a specified renewal days before expiration threshold at the time of issuance; for more information + about server-side auto-renewal, refer to the documentation [here](/documentation/platform/pki/certificates/certificates#guide-to-renewing-certificates). + + + + To create a certificate, head to your Project > Certificates > Certificates and press **Issue**. + + ![pki certificates](/images/platform/pki/certificate/cert-issue.png) + +Here, select the certificate profile from step 1 that will be used to issue the certificate and fill out the rest of the details for the certificate to be issued. + +![pki certificate issue modal](/images/platform/pki/certificate/cert-issue-modal.png) + + + + Once you have created the certificate from step 1, you'll be presented with the certificate details including the **Certificate Body**, **Certificate Chain**, and **Private Key**. + + ![pki certificate body](/images/platform/pki/certificate/cert-body.png) + + + Make sure to download and store the **Private Key** in a secure location as it + will only be displayed once at the time of certificate issuance. The + **Certificate Body** and **Certificate Chain** will remain accessible and can + be copied at any time. + + + + + + + + + + + To create a certificate [profile](/documentation/platform/pki/certificates/profiles), make an API request to the [Create Certificate Profile](/docs/api-reference/endpoints/certificate-profiles/create) API endpoint. + + ### Sample request + + ```bash Request + curl --location --request POST 'https://app.infisical.com/api/v1/pki/certificate-profiles' \ + --header 'Authorization: Bearer ' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "projectId": "", + "caId": "", + "certificateTemplateId": "", + "slug": "my-api-profile", + "description": "Certificate profile for API enrollment", + "enrollmentType": "API", + "apiConfig": { + "autoRenew": true, + "renewBeforeDays": 7 + } + }' + ``` + + ### Sample response + + ```bash Response + { + "certificateProfile": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "projectId": "65f0a4b0-c123-4567-8901-23456789abcd", + "caId": "550e8400-e29b-41d4-a716-446655440000", + "certificateTemplateId": "660f1234-e29b-41d4-a716-446655440001", + "slug": "my-api-profile", + "description": "Certificate profile for API enrollment", + "enrollmentType": "API", + "apiConfigId": "770g2345-e29b-41d4-a716-446655440002", + "createdAt": "2023-01-19T09:44:36.267Z", + "updatedAt": "2023-01-19T09:44:36.267Z" + } + } + ``` + + + + + To issue a certificate against the certificate profile, make an API request to the [Issue Certificate](/api-reference/endpoints/certificates/issue-certificate) API endpoint. + + ### Sample request + + ```bash Request + curl --location --request POST 'https://app.infisical.com/api/v3/pki/certificates/issue-certificate' \ + --header 'Authorization: Bearer ' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "profileId": "", + "commonName": "service.acme.com", + "ttl": "1y", + "signatureAlgorithm": "RSA-SHA256", + "keyAlgorithm": "RSA_2048", + "keyUsages": ["digital_signature", "key_encipherment"], + "extendedKeyUsages": ["server_auth"], + "altNames": [ + { + "type": "DNS", + "value": "service.acme.com" + }, + { + "type": "DNS", + "value": "www.service.acme.com" + } + ] + }' + ``` + + ### Sample response + + ```bash Response + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIEpDCCAowCCQD...\n-----END CERTIFICATE-----", + "certificateChain": "-----BEGIN CERTIFICATE-----\nMIIEpDCCAowCCQD...\n-----END CERTIFICATE-----", + "issuingCaCertificate": "-----BEGIN CERTIFICATE-----\nMIIEpDCCAowCCQD...\n-----END CERTIFICATE-----", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...\n-----END PRIVATE KEY-----", + "serialNumber": "123456789012345678", + "certificateId": "880h3456-e29b-41d4-a716-446655440003" + } + ``` + + + Make sure to store the `privateKey` as it is only returned once here at the time of certificate issuance. The `certificate` and `certificateChain` will remain accessible and can be retrieved at any time. + + + If you have an external private key, you can also issue a certificate by making an API request containing a pem-encoded CSR (Certificate Signing Request) to the [Sign Certificate](/api-reference/endpoints/certificates/sign-certificate) API endpoint. + + ### Sample request + + ```bash Request + curl --location --request POST 'https://app.infisical.com/api/v3/pki/certificates/sign-certificate' \ + --header 'Authorization: Bearer ' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "profileId": "", + "csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIICvDCCAaQCAQAwdzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBE9oaW8...\n-----END CERTIFICATE REQUEST-----", + "ttl": "1y" + }' + ``` + + ### Sample response + + ```bash Response + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIEpDCCAowCCQD...\n-----END CERTIFICATE-----", + "certificateChain": "-----BEGIN CERTIFICATE-----\nMIIEpDCCAowCCQD...\n-----END CERTIFICATE-----", + "issuingCaCertificate": "-----BEGIN CERTIFICATE-----\nMIIEpDCCAowCCQD...\n-----END CERTIFICATE-----", + "serialNumber": "123456789012345679", + "certificateId": "990i4567-e29b-41d4-a716-446655440004" + } + ``` + + + + + diff --git a/docs/documentation/platform/pki/enrollment-methods/est.mdx b/docs/documentation/platform/pki/enrollment-methods/est.mdx new file mode 100644 index 0000000000..a4e463a2f4 --- /dev/null +++ b/docs/documentation/platform/pki/enrollment-methods/est.mdx @@ -0,0 +1,71 @@ +--- +title: "Certificate Enrollment via EST" +sidebarTitle: "EST" +--- + +## Concept + +The API enrollment method allows you to issue and manage certificates against a specific certificate profile using the [EST protocol](https://en.wikipedia.org/wiki/Enrollment_over_Secure_Transport). +This method is suitable for environments requiring strong authentication and encrypted communication, such as in IoT, enterprise networks, and secure web services. + +Infisical's EST service is based on [RFC 7030](https://datatracker.ietf.org/doc/html/rfc7030) and implements the following endpoints: + +- **cacerts** - provides the necessary CA chain for the client to validate certificates issued by the CA. +- **simpleenroll** - allows an EST client to request a new certificate from Infisical's EST server +- **simplereenroll** - similar to the /simpleenroll endpoint but is used for renewing an existing certificate. + +These EST endpoints are exposed on port 8443 under the .well-known/est path +and structured under `https://app.infisical.com:8443/.well-known/est/{profile_id}/...` + +## Prerequisites + +- Your client devices need to have a bootstrap/pre-installed certificate. +- Your client devices must trust the server certificates used by Infisical's EST server. If the devices are new or lack existing trust configurations, you need to manually establish trust for the appropriate certificates. + + + For Infisical Cloud users, the devices must be configured to trust the [Amazon + root CA certificates](https://www.amazontrust.com/repository). + + +## Guide to Certificate Enrollment via EST + +In the following steps, we explore how to issue a X.509 certificate using the EST enrollment method. + + + + Create a [certificate + profile](/documentation/platform/pki/certificates/profiles) with **EST** + selected as the enrollment method and fill in EST-specific configuration. + + ![pki est config](/images/platform/pki/enrollment-methods/est/est-config.png) + + Here's some guidance on each EST-specific configuration field: + + - Disable Bootstrap CA Validation: Enable this if your devices are not configured with a bootstrap certificate. + - EST Passphrase: This is also used to authenticate your devices with Infisical's EST server. When configuring the clients, use the value defined here as the EST password. + - CA Chain Certificate: This is the certificate chain used to validate your devices' manufacturing/pre-installed certificates. This will be used to authenticate your devices with Infisical's EST server. + + + + Once the EST enrollment method configuration is complete, you can use the ID of the associated certificate profile + `profile_id` as the EST label when enrolling EST clients with Infisical. + + ![pki est label](/images/platform/pki/enrollment-methods/est/est-label.png) + + The complete URL structure of the supported EST endpoints may look like the following: + + - https://app.infisical.com:8443/.well-known/est/{profile_id}/cacerts + - https://app.infisical.com:8443/.well-known/est/{profile_id}/simpleenroll + - https://app.infisical.com:8443/.well-known/est/{profile_id}/simplereenroll + + + + To use the EST passphrase in your clients, configure it as the EST password. The EST username can be set to any arbitrary value. + Use the appropriate client certificates for invoking the EST endpoints. + - For `simpleenroll`, use the bootstrapped/manufacturer client certificate. + - For `simplereenroll`, use a valid EST-issued client certificate. + When configuring the PKCS#12 objects for the client certificates, only include the leaf certificate and the private key. + + + + diff --git a/docs/documentation/platform/pki/enrollment-methods/overview.mdx b/docs/documentation/platform/pki/enrollment-methods/overview.mdx new file mode 100644 index 0000000000..f1af9375d1 --- /dev/null +++ b/docs/documentation/platform/pki/enrollment-methods/overview.mdx @@ -0,0 +1,11 @@ +--- +title: "Overview" +sidebarTitle: "Overview" +--- + +Enrollment methods determine how certificates are issued and managed for a [certificate profile](/documentation/platform/pki/certificates/profiles). + +Refer to the documentation for each enrollment method to learn more about how to enroll certificates using it. + +- [API](/documentation/platform/pki/enrollment-methods/api): Enroll certificates via API. +- [EST](/documentation/platform/pki/enrollment-methods/est): Enroll certificates via EST protocol. diff --git a/docs/documentation/platform/pki/enrollment-methods/scep.mdx b/docs/documentation/platform/pki/enrollment-methods/scep.mdx new file mode 100644 index 0000000000..977e7b388e --- /dev/null +++ b/docs/documentation/platform/pki/enrollment-methods/scep.mdx @@ -0,0 +1,8 @@ +--- +title: "Certificate Enrollment via SCEP" +sidebarTitle: "SCEP" +--- + + + SCEP-based certificate enrollment is currently under development and will be included in a future release. + diff --git a/docs/documentation/platform/pki/external-ca.mdx b/docs/documentation/platform/pki/external-ca.mdx deleted file mode 100644 index efe029149f..0000000000 --- a/docs/documentation/platform/pki/external-ca.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: "External CA" -sidebarTitle: "External CA" -description: "Learn how to connect External Certificate Authorities with Infisical." ---- - -## Concept - -In addition to creating a Private CA hierarchy, Infisical allows you to integrate with External Certificate Authorities (CAs) to issue digital certificates for your [subscribers](/documentation/platform/pki/subscribers). This integration enables you to leverage established certificate authority infrastructure while centralizing your certificate management within Infisical. - -
- -```mermaid -graph TD - B[Infisical] -->|Manages Certificates| D[Subscribers] - - A1[Public CAs
Let's Encrypt, ZeroSSL] -->|ACME Protocol| B - A2[Enterprise CAs
Vault PKI, Step CA] -->|ACME Protocol| B - A3[Cloud CAs
ACME-compatible services] -->|ACME Protocol| B - - A4[Future: Enterprise CAs] -.->|EST/SCEP Protocols| B - A5[Future: Cloud CAs] -.->|REST APIs| B -``` - -
- -When you integrate an External CA with Infisical, you benefit from: - -1. **Trust by Default**: Certificates issued by public CAs are trusted by default in browsers and operating systems. -2. **Unified Management**: Manage all certificates—both internally and externally issued—from a single platform. -3. **Automation**: Leverage Infisical's automation capabilities for certificate lifecycle management. -4. **Compliance**: Meet requirements for publicly trusted certificates, especially for public-facing services. -5. **Flexibility**: Choose the most appropriate CA for different use cases while maintaining consistent management. - -## General Workflow - -A typical workflow for integrating an External CA with Infisical consists of the following steps: - -1. **Select External CA Type**: Choose the appropriate external CA based on your requirements and supported protocols. -2. **Configure Prerequisites**: Set up any required credentials, connections, or configurations specific to your chosen CA type. -3. **Register External CA**: Add the External CA configuration to your Infisical project. -4. **Create Subscribers**: Set up subscribers that use the External CA as their issuing authority. -5. **Manage Certificate Lifecycle**: Handle certificate issuance, renewal, and revocation through Infisical's unified interface. - -The specific steps and requirements vary depending on the External CA type you choose to integrate. - -## Supported Integration Methods - -Infisical currently supports integration with External Certificate Authorities through the following protocol: - -### ACME Protocol Integration - -ACME (Automatic Certificate Management Environment) is a widely adopted protocol for automated certificate issuance and management. Infisical can integrate with any CA that supports the ACME protocol, including: - -**Public Certificate Authorities:** -- Let's Encrypt - Free, automated SSL/TLS certificates -- ZeroSSL - Free and premium SSL certificates -- Buypass - Norwegian CA with free ACME certificates - -**Enterprise Certificate Authorities:** -- HashiCorp Vault PKI - Enterprise secret management with ACME support -- Step CA - Open-source certificate authority with ACME - -**Cloud Certificate Authorities:** -- Some managed certificate services that support ACME protocol - -[Learn more about ACME integration →](/documentation/platform/pki/acme-ca) - -## Use Cases - -External CA integration is ideal for various scenarios: - -### Public-Facing Services -Use publicly trusted CAs for websites and services that need browser compatibility: -- Web applications and APIs -- Load balancers and CDNs -- Public-facing microservices - -### Compliance Requirements -Meet specific compliance standards that require certificates from accredited CAs: -- PCI DSS compliance -- SOC 2 requirements -- Industry-specific regulations - -### Hybrid Infrastructure -Combine internal and external CAs for different use cases: -- Internal services with Private CAs -- Public services with External CAs -- Development vs. production environments - -### Legacy System Integration -Integrate with existing enterprise PKI infrastructure: -- Windows Active Directory Certificate Services -- Network device management -- IoT device provisioning - -## Benefits of Centralized Management - -Managing External CAs through Infisical provides several advantages over direct CA management: - -### Unified Certificate Inventory -- Single dashboard for all certificates -- Centralized expiration tracking -- Cross-CA certificate analytics - -### Automated Lifecycle Management -- Automatic certificate reissuance before expiration -- Proactive expiration alerts -- Standardized certificate management processes - -### Enhanced Security -- Centralized access controls -- Audit trails for all certificate operations -- Policy enforcement across CAs - -### Operational Efficiency -- Reduced manual certificate management -- Consistent deployment workflows -- API-driven automation -- Integration with existing tools - -## Available Integration Guides - -Get started with External CA integration: - - - - Set up automated certificate issuance with any ACME-compatible CA - - - Custom CA integrations via REST APIs (Coming Soon) - - - -## FAQ - - - - Currently, Infisical supports any Certificate Authority that implements the ACME protocol, including: - - - **Public CAs**: Let's Encrypt, ZeroSSL, Buypass - - **Enterprise CAs**: HashiCorp Vault PKI, Step CA - - **Cloud CAs**: ACME-compatible managed services - - Integration uses DNS-01 validation through Route53 or Cloudflare. Learn more about [supported DNS validation methods](/documentation/platform/pki/acme-ca#what-dns-validation-methods-are-supported). - - Support for additional integration protocols (EST, SCEP, direct APIs) is planned for future releases. - - - Yes. You can have both Private CAs (root and intermediate) and External CAs in the same project, allowing you flexibility in how you issue certificates for different use cases. This hybrid approach enables you to: - - - Use Private CAs for internal services and applications - - Use External CAs for public-facing services - - Apply consistent management practices across all certificate types - - Implement appropriate security controls based on certificate usage - - - The types of certificates you can issue depend on the External CA provider and type: - - - **Public CAs**: Typically support Domain Validation (DV) certificates, with some offering Organization Validation (OV) - - **Enterprise CAs**: Support internal certificates, device certificates, and custom certificate types - - **Cloud CAs**: Support various certificate types depending on the service - - Certificate capabilities vary by provider and integration method. - - - Certificate reissuance is handled automatically by Infisical based on the CA type: - - - **Public CAs**: Automatic reissuance using ACME protocol with the same certificate extensions before expiration - - **Other CA types**: Certificate management methods depend on the specific integration (when available) - - All certificate lifecycle events are tracked and managed through Infisical's unified interface, ensuring continuous certificate validity. - - - Authentication methods vary by CA type: - - - **Public CAs**: ACME account registration with email and account keys - - **Enterprise CAs**: Client certificates, username/password, or domain authentication (when available) - - **Cloud CAs**: API keys, OAuth tokens, or service account authentication (when available) - - Infisical securely stores and manages all authentication credentials. - - - Yes, Infisical provides policy enforcement capabilities: - - - Certificate template constraints - - Monitoring and alerting policies - - Access controls for certificate operations - - These policies ensure consistent governance across both internal and external certificate sources. - - diff --git a/docs/documentation/platform/pki/integration-guides/gloo-mesh.mdx b/docs/documentation/platform/pki/integration-guides/gloo-mesh.mdx index 2a04f5e3a0..d1f1273fd0 100644 --- a/docs/documentation/platform/pki/integration-guides/gloo-mesh.mdx +++ b/docs/documentation/platform/pki/integration-guides/gloo-mesh.mdx @@ -1,5 +1,5 @@ --- -title: "Gloo Mesh Integration" +title: "Gloo Mesh" description: "Learn how to automatically provision and manage Istio intermediate CA certificates for Gloo Mesh using Infisical PKI" --- @@ -7,8 +7,8 @@ This guide will provide a high level overview on how you can use Infisical PKI a ## Overview -In this setup, we will use Infisical PKI to generate and store your root CA and subordinate CAs that are used to generate Istio intermediate CAs for your Gloo Mesh workload clusters. -To manage the lifecycle of Istio intermediate CA certificates, you'll also install [cert-manager](https://cert-manager.io/). +In this setup, we will use Infisical PKI to generate and store your root CA and subordinate CAs that are used to generate Istio intermediate CAs for your Gloo Mesh workload clusters. +To manage the lifecycle of Istio intermediate CA certificates, you'll also install [cert-manager](https://cert-manager.io/). Cert-manager is a Kubernetes controller that helps you automate the process of obtaining and renewing certificates from various PKI providers. With this approach, you get the following benefits: @@ -18,10 +18,10 @@ With this approach, you get the following benefits: - Use cert-manager to automatically issue and renew Istio intermediate CA certificates from the same root, ensuring cross-cluster workload communication. - Increased auditability of private key infrastructure. - ## General Setup + The certificate provisioning workflow begins with setting up your PKI hierarchy in Infisical, where you create root and subordinate certificate authorities. -When you deploy a `Certificate` CRD in your workload cluster, `cert-manager` uses the Infisical PKI Issuer controller to authenticate with Infisical using machine identity credentials and request an intermediate CA certificate. +When you deploy a `Certificate` CRD in your workload cluster, `cert-manager` uses the Infisical PKI Issuer controller to authenticate with Infisical using machine identity credentials and request an intermediate CA certificate. Infisical verifies the request against your certificate templates and returns the signed certificate. From there, Istio's control plane will automatically use this intermediate CA to sign leaf certificates for workloads in the service mesh, enabling secure mTLS communication across your entire Gloo Mesh infrastructure. @@ -35,5 +35,5 @@ For Gloo Mesh-specific configuration, ensure that: ## Using the certificates -Once the `cacerts` Kubernetes secret is created in the `istio-system` namespace, Istio automatically uses the custom CA certificate instead of the default self-signed certificate. -When you deploy applications to your Gloo Mesh service mesh, the workloads will receive leaf certificates signed by your Infisical PKI intermediate CA, enabling secure mTLS communication across your entire mesh infrastructure. \ No newline at end of file +Once the `cacerts` Kubernetes secret is created in the `istio-system` namespace, Istio automatically uses the custom CA certificate instead of the default self-signed certificate. +When you deploy applications to your Gloo Mesh service mesh, the workloads will receive leaf certificates signed by your Infisical PKI intermediate CA, enabling secure mTLS communication across your entire mesh infrastructure. diff --git a/docs/documentation/platform/pki/overview.mdx b/docs/documentation/platform/pki/overview.mdx index b2351813c4..d31bd96d9e 100644 --- a/docs/documentation/platform/pki/overview.mdx +++ b/docs/documentation/platform/pki/overview.mdx @@ -4,10 +4,16 @@ sidebarTitle: "Overview" description: "Learn how to create a Private CA hierarchy and issue X.509 certificates." --- -Infisical can be used to create and manage Certificate Authorities (CAs) and issue X.509 certificates. This allows you to manage PKI infrastructure and issue digital certificates for subscribers such as services, applications, and devices. +Infisical can be used to create and manage Certificate Authorities (CAs) and issue digital X.509 certificates. This allows you to manage PKI infrastructure and issue certificates for end-entities such as load balancers, web servers, devices, and more. -Infisical's PKI offering is split into three components: +It helps teams automate certificate management including enrollment and renewal, and adopt secure workflows to ensure certificates remain valid, trusted, and synchronized across infrastructure. -- [Certificate Authorities](/documentation/platform/pki/private-ca): Create and manage CAs, including root and intermediate CAs. -- [Subscribers](/documentation/platform/pki/subscribers): Define and manage entities that will request X.509 certificates from CAs. This module provides a centralized view of all subscribers, enabling you to issue certificates and monitor their status. -- [Certificates](/documentation/platform/pki/certificates): Track and monitor issued X.509 certificates, maintaining a comprehensive inventory of all active and expired certificates. +Core capabilities include: + +- [Private CA](/documentation/platform/pki/ca/private-ca): Create and manage your own private CA hierarchy including root and intermediate CAs. +- [External CA integration](/documentation/platform/pki/ca/external-ca): Integrate with external public and private CAs including [Azure ADCS](/documentation/platform/pki/ca/azure-adcs) and [ACME-compatible CAs](/documentation/platform/pki/ca/acme-ca) like Let's Encrypt and DigiCert. +- [Certificate Enrollment](/documentation/platform/pki/enrollment-methods/overview): Support enrollment methods including [API](/documentation/platform/pki/enrollment-methods/api), ACME, [EST](/documentation/platform/pki/enrollment-methods/est), and more to automate certificate issuance for services, devices, and workloads. +- Certificate Inventory: Track and monitor issued X.509 certificates, maintaining a comprehensive inventory of all active and expired certificates. +- Certificate Lifecycle Automation: Automate issuance, [renewal](/documentation/platform/pki/certificates/certificates#guide-to-renewing-certificates), and [revocation](/documentation/platform/pki/certificates/certificates#guide-to-revoking-certificates) with policy-based workflows, ensuring certificates remain valid, compliant, and up to date across your infrastructure. +- [Certificate Syncs](/documentation/platform/pki/certificate-syncs/overview): Push certificates to cloud certificate managers like [AWS Certificate Manager](/documentation/platform/pki/certificate-syncs/aws-certificate-manager) and [Azure Key Vault](/documentation/platform/pki/certificate-syncs/azure-key-vault). +- [Certificate Alerts](/documentation/platform/pki/alerting): Receive alerts and webhook events for certificate lifecycle changes such as certificate expiration. diff --git a/docs/documentation/platform/pki/pki-issuer.mdx b/docs/documentation/platform/pki/pki-issuer.mdx index 13214bfb77..a1d07c98bb 100644 --- a/docs/documentation/platform/pki/pki-issuer.mdx +++ b/docs/documentation/platform/pki/pki-issuer.mdx @@ -1,5 +1,5 @@ --- -title: "Cert Manager Issuer" +title: "Kubernetes Issuer" description: "Learn how to automatically provision and manage TLS certificates in Kubernetes using Infisical PKI" --- diff --git a/docs/documentation/platform/secrets-mgmt/concepts/secrets-mgmt.mdx b/docs/documentation/platform/secrets-mgmt/concepts/secrets-mgmt.mdx index 9fe68fb071..8e19b09371 100644 --- a/docs/documentation/platform/secrets-mgmt/concepts/secrets-mgmt.mdx +++ b/docs/documentation/platform/secrets-mgmt/concepts/secrets-mgmt.mdx @@ -3,7 +3,7 @@ title: "Secrets Management" description: "Learn what is secrets management and why it matters for building secure systems." --- -## What is Secret? +## What is a Secret? A _secret_ is a confidential value used by an application such as database credential, API key, or other configuration. diff --git a/docs/images/app-connections/azure/client-secrets/create-certificate-method.png b/docs/images/app-connections/azure/client-secrets/create-certificate-method.png new file mode 100644 index 0000000000..37f6423102 Binary files /dev/null and b/docs/images/app-connections/azure/client-secrets/create-certificate-method.png differ diff --git a/docs/images/app-connections/azure/client-secrets/upload-certificate.png b/docs/images/app-connections/azure/client-secrets/upload-certificate.png new file mode 100644 index 0000000000..518c70b2dd Binary files /dev/null and b/docs/images/app-connections/azure/client-secrets/upload-certificate.png differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/acm-destination.png b/docs/images/certificate-syncs/aws-certificate-manager/acm-destination.png deleted file mode 100644 index 42a20fc996..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/acm-destination.png and /dev/null differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/acm-details.png b/docs/images/certificate-syncs/aws-certificate-manager/acm-details.png deleted file mode 100644 index 483cee003d..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/acm-details.png and /dev/null differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/acm-options.png b/docs/images/certificate-syncs/aws-certificate-manager/acm-options.png deleted file mode 100644 index aa08b2d193..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/acm-options.png and /dev/null differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/acm-review.png b/docs/images/certificate-syncs/aws-certificate-manager/acm-review.png deleted file mode 100644 index 5f7b216ad2..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/acm-review.png and /dev/null differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/acm-source.png b/docs/images/certificate-syncs/aws-certificate-manager/acm-source.png deleted file mode 100644 index 0d92fe69e1..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/acm-source.png and /dev/null differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/acm-synced.png b/docs/images/certificate-syncs/aws-certificate-manager/acm-synced.png deleted file mode 100644 index 7e1ed12c52..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/acm-synced.png and /dev/null differ diff --git a/docs/images/certificate-syncs/aws-certificate-manager/select-acm-option.png b/docs/images/certificate-syncs/aws-certificate-manager/select-acm-option.png deleted file mode 100644 index 79515516a4..0000000000 Binary files a/docs/images/certificate-syncs/aws-certificate-manager/select-acm-option.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/select-key-vault-option.png b/docs/images/certificate-syncs/azure-key-vault/select-key-vault-option.png deleted file mode 100644 index 0a9093ee88..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/select-key-vault-option.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/vault-destination.png b/docs/images/certificate-syncs/azure-key-vault/vault-destination.png deleted file mode 100644 index 96295362b6..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/vault-destination.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/vault-details.png b/docs/images/certificate-syncs/azure-key-vault/vault-details.png deleted file mode 100644 index cc2ebc6919..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/vault-details.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/vault-options.png b/docs/images/certificate-syncs/azure-key-vault/vault-options.png deleted file mode 100644 index 980dad0ef2..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/vault-options.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/vault-review.png b/docs/images/certificate-syncs/azure-key-vault/vault-review.png deleted file mode 100644 index 4853be086c..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/vault-review.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/vault-source.png b/docs/images/certificate-syncs/azure-key-vault/vault-source.png deleted file mode 100644 index 5ab8c5dc60..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/vault-source.png and /dev/null differ diff --git a/docs/images/certificate-syncs/azure-key-vault/vault-synced.png b/docs/images/certificate-syncs/azure-key-vault/vault-synced.png deleted file mode 100644 index 2bacd1a7b0..0000000000 Binary files a/docs/images/certificate-syncs/azure-key-vault/vault-synced.png and /dev/null differ diff --git a/docs/images/certificate-syncs/general/certificate-sync-tab.png b/docs/images/certificate-syncs/general/certificate-sync-tab.png deleted file mode 100644 index f97a10aa3e..0000000000 Binary files a/docs/images/certificate-syncs/general/certificate-sync-tab.png and /dev/null differ diff --git a/docs/images/platform/pki/ca-crl.png b/docs/images/platform/pki/ca-crl.png deleted file mode 100644 index efe7d3b4ab..0000000000 Binary files a/docs/images/platform/pki/ca-crl.png and /dev/null differ diff --git a/docs/images/platform/pki/ca/ca-create-intermediate.png b/docs/images/platform/pki/ca/ca-create-intermediate.png index ac83db3e98..fe87345e6d 100644 Binary files a/docs/images/platform/pki/ca/ca-create-intermediate.png and b/docs/images/platform/pki/ca/ca-create-intermediate.png differ diff --git a/docs/images/platform/pki/ca/ca-create-root.png b/docs/images/platform/pki/ca/ca-create-root.png index a8bf936a3c..acf2d6a7af 100644 Binary files a/docs/images/platform/pki/ca/ca-create-root.png and b/docs/images/platform/pki/ca/ca-create-root.png differ diff --git a/docs/images/platform/pki/ca/ca-create.png b/docs/images/platform/pki/ca/ca-create.png index 915ed684c0..f8cedd572a 100644 Binary files a/docs/images/platform/pki/ca/ca-create.png and b/docs/images/platform/pki/ca/ca-create.png differ diff --git a/docs/images/platform/pki/ca/ca-crl.png b/docs/images/platform/pki/ca/ca-crl.png new file mode 100644 index 0000000000..9612974099 Binary files /dev/null and b/docs/images/platform/pki/ca/ca-crl.png differ diff --git a/docs/images/platform/pki/ca/ca-install-intermediate-csr.png b/docs/images/platform/pki/ca/ca-install-intermediate-csr.png index 77c7df0b97..b35b2ab10f 100644 Binary files a/docs/images/platform/pki/ca/ca-install-intermediate-csr.png and b/docs/images/platform/pki/ca/ca-install-intermediate-csr.png differ diff --git a/docs/images/platform/pki/ca/ca-install-intermediate-opt.png b/docs/images/platform/pki/ca/ca-install-intermediate-opt.png index 16afd2f0b8..4fdd64af77 100644 Binary files a/docs/images/platform/pki/ca/ca-install-intermediate-opt.png and b/docs/images/platform/pki/ca/ca-install-intermediate-opt.png differ diff --git a/docs/images/platform/pki/ca/ca-install-intermediate.png b/docs/images/platform/pki/ca/ca-install-intermediate.png index 10c9424ff6..c0a0adde5c 100644 Binary files a/docs/images/platform/pki/ca/ca-install-intermediate.png and b/docs/images/platform/pki/ca/ca-install-intermediate.png differ diff --git a/docs/images/platform/pki/ca/cas.png b/docs/images/platform/pki/ca/cas.png index d3189fd1af..628b03a34a 100644 Binary files a/docs/images/platform/pki/ca/cas.png and b/docs/images/platform/pki/ca/cas.png differ diff --git a/docs/images/platform/pki/cert-revoke-modal.png b/docs/images/platform/pki/cert-revoke-modal.png deleted file mode 100644 index 07bc7fce86..0000000000 Binary files a/docs/images/platform/pki/cert-revoke-modal.png and /dev/null differ diff --git a/docs/images/platform/pki/cert-revoke.png b/docs/images/platform/pki/cert-revoke.png deleted file mode 100644 index ff7fcc5978..0000000000 Binary files a/docs/images/platform/pki/cert-revoke.png and /dev/null differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-certificates.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-certificates.png new file mode 100644 index 0000000000..42d83c0f7a Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-certificates.png differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-destination.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-destination.png new file mode 100644 index 0000000000..6d04606010 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-destination.png differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-details.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-details.png new file mode 100644 index 0000000000..8d7757fd09 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-details.png differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-options.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-options.png new file mode 100644 index 0000000000..03254ee9f1 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-options.png differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-review.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-review.png new file mode 100644 index 0000000000..520b5e7f77 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-review.png differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-synced.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-synced.png new file mode 100644 index 0000000000..f6b40afa7d Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/acm-synced.png differ diff --git a/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/select-acm-option.png b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/select-acm-option.png new file mode 100644 index 0000000000..b1b79dda51 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/aws-certificate-manager/select-acm-option.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-certificates.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-certificates.png new file mode 100644 index 0000000000..04040365aa Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-certificates.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-destination.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-destination.png new file mode 100644 index 0000000000..05361ae81f Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-destination.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-details.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-details.png new file mode 100644 index 0000000000..642eeef34f Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-details.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-options.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-options.png new file mode 100644 index 0000000000..69cd37d216 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-options.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-review.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-review.png new file mode 100644 index 0000000000..ae0c965748 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-review.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-synced.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-synced.png new file mode 100644 index 0000000000..157b4bf326 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/akv-synced.png differ diff --git a/docs/images/platform/pki/certificate-syncs/azure-key-vault/select-akv-option.png b/docs/images/platform/pki/certificate-syncs/azure-key-vault/select-akv-option.png new file mode 100644 index 0000000000..1fa0c0ca11 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/azure-key-vault/select-akv-option.png differ diff --git a/docs/images/platform/pki/certificate-syncs/general/create-certificate-sync.png b/docs/images/platform/pki/certificate-syncs/general/create-certificate-sync.png new file mode 100644 index 0000000000..f0f62f3862 Binary files /dev/null and b/docs/images/platform/pki/certificate-syncs/general/create-certificate-sync.png differ diff --git a/docs/images/platform/pki/certificate/cert-body.png b/docs/images/platform/pki/certificate/cert-body.png index 8c1433b54e..02c61840a6 100644 Binary files a/docs/images/platform/pki/certificate/cert-body.png and b/docs/images/platform/pki/certificate/cert-body.png differ diff --git a/docs/images/platform/pki/certificate/cert-issue-modal.png b/docs/images/platform/pki/certificate/cert-issue-modal.png index 352c4979c5..e3de549bb6 100644 Binary files a/docs/images/platform/pki/certificate/cert-issue-modal.png and b/docs/images/platform/pki/certificate/cert-issue-modal.png differ diff --git a/docs/images/platform/pki/certificate/cert-issue.png b/docs/images/platform/pki/certificate/cert-issue.png index 614271d193..4506b95523 100644 Binary files a/docs/images/platform/pki/certificate/cert-issue.png and b/docs/images/platform/pki/certificate/cert-issue.png differ diff --git a/docs/images/platform/pki/certificate/cert-profile-modal.png b/docs/images/platform/pki/certificate/cert-profile-modal.png new file mode 100644 index 0000000000..29280d01c0 Binary files /dev/null and b/docs/images/platform/pki/certificate/cert-profile-modal.png differ diff --git a/docs/images/platform/pki/certificate/cert-profile.png b/docs/images/platform/pki/certificate/cert-profile.png new file mode 100644 index 0000000000..3337f80652 Binary files /dev/null and b/docs/images/platform/pki/certificate/cert-profile.png differ diff --git a/docs/images/platform/pki/certificate/cert-revoke-modal.png b/docs/images/platform/pki/certificate/cert-revoke-modal.png new file mode 100644 index 0000000000..5b47dc7ca5 Binary files /dev/null and b/docs/images/platform/pki/certificate/cert-revoke-modal.png differ diff --git a/docs/images/platform/pki/certificate/cert-revoke.png b/docs/images/platform/pki/certificate/cert-revoke.png new file mode 100644 index 0000000000..bc6bdb47c8 Binary files /dev/null and b/docs/images/platform/pki/certificate/cert-revoke.png differ diff --git a/docs/images/platform/pki/certificate/cert-template-modal.png b/docs/images/platform/pki/certificate/cert-template-modal.png index 2f6c881668..970b2f2f1a 100644 Binary files a/docs/images/platform/pki/certificate/cert-template-modal.png and b/docs/images/platform/pki/certificate/cert-template-modal.png differ diff --git a/docs/images/platform/pki/certificate/cert-template.png b/docs/images/platform/pki/certificate/cert-template.png new file mode 100644 index 0000000000..88080e7ca8 Binary files /dev/null and b/docs/images/platform/pki/certificate/cert-template.png differ diff --git a/docs/images/platform/pki/enrollment-methods/est/est-config.png b/docs/images/platform/pki/enrollment-methods/est/est-config.png new file mode 100644 index 0000000000..63892a99da Binary files /dev/null and b/docs/images/platform/pki/enrollment-methods/est/est-config.png differ diff --git a/docs/images/platform/pki/enrollment-methods/est/est-label.png b/docs/images/platform/pki/enrollment-methods/est/est-label.png new file mode 100644 index 0000000000..76f77a2cb6 Binary files /dev/null and b/docs/images/platform/pki/enrollment-methods/est/est-label.png differ diff --git a/docs/images/platform/pki/est/template-enroll-hover.png b/docs/images/platform/pki/est/template-enroll-hover.png deleted file mode 100644 index 7bec8e3f63..0000000000 Binary files a/docs/images/platform/pki/est/template-enroll-hover.png and /dev/null differ diff --git a/docs/images/platform/pki/est/template-enrollment-est-label.png b/docs/images/platform/pki/est/template-enrollment-est-label.png deleted file mode 100644 index 4ad7bbeb1b..0000000000 Binary files a/docs/images/platform/pki/est/template-enrollment-est-label.png and /dev/null differ diff --git a/docs/images/platform/pki/est/template-enrollment-modal.png b/docs/images/platform/pki/est/template-enrollment-modal.png deleted file mode 100644 index 4ce08cfe06..0000000000 Binary files a/docs/images/platform/pki/est/template-enrollment-modal.png and /dev/null differ diff --git a/docs/integrations/app-connections/azure-client-secrets.mdx b/docs/integrations/app-connections/azure-client-secrets.mdx index cb25fb5968..4b55a73544 100644 --- a/docs/integrations/app-connections/azure-client-secrets.mdx +++ b/docs/integrations/app-connections/azure-client-secrets.mdx @@ -66,29 +66,72 @@ Infisical currently only supports two methods for connecting to Azure, which are - - Ensure your Azure application has the required permissions that Infisical needs for the Azure Client Secrets connection to work. - **Prerequisites:** - - An active Azure setup. + - - - For the Azure Client Secrets connection to work, assign the following permissions to your Azure application: + + Ensure your Azure application has the required permissions that Infisical needs for the Azure Client Secrets connection to work. + + **Prerequisites:** + - An active Azure setup. + + + + For the Azure Client Secrets connection to work, assign the following permissions to your Azure application: + + #### Required API Permissions + + **Microsoft Graph** + - `Application.ReadWrite.All` + - `Application.ReadWrite.OwnedBy` + - `Application.ReadWrite.All` (Delegated) + - `Directory.ReadWrite.All` (Delegated) + - `User.Read` (Delegated) + + ![Azure client secrets](/images/integrations/azure-client-secrets/app-api-permissions.png) + + + + + Ensure your Azure application has the required permissions that Infisical needs for the Azure Client Secrets connection to work. + + **Prerequisites:** + - An active Azure setup. + + + + For the Azure Client Secrets connection to work, assign the following permissions to your Azure application: + + #### Required API Permissions + + **Microsoft Graph** + - `Application.ReadWrite.All` + - `Application.ReadWrite.OwnedBy` + - `Application.ReadWrite.All` (Delegated) + - `Directory.ReadWrite.All` (Delegated) + - `User.Read` (Delegated) + + ![Azure client secrets](/images/integrations/azure-client-secrets/app-api-permissions.png) + + + + Navigate to the **Certificates & secrets** section of your Azure App Registration, and press the **Upload certificate** button. + + Select the **Upload** button and upload your certificate. + + ![Upload certificate](/images/app-connections/azure/client-secrets/upload-certificate.png) + + + Keep in mind that both the certificate and its private key are required to configure the Azure Client Secrets connection in Infisical. + + + + + + + - #### Required API Permissions - - **Microsoft Graph** - - `Application.ReadWrite.All` - - `Application.ReadWrite.OwnedBy` - - `Application.ReadWrite.All` (Delegated) - - `Directory.ReadWrite.All` (Delegated) - - `User.Read` (Delegated) - ![Azure client secrets](/images/integrations/azure-client-secrets/app-api-permissions.png) - - - ## Setup Azure Connection in Infisical @@ -123,6 +166,17 @@ Infisical currently only supports two methods for connecting to Azure, which are ![Connect via Azure OAUth](/images/app-connections/azure/client-secrets/create-client-secrets-method.png)
+ + + Fill in the **Tenant ID**, **Client ID**, **Certificate (PEM format)**, and **Private Key** fields with the Directory (Tenant) ID, Application (Client) ID, Certificate and Private Key you obtained in the [previous step](#certificate-authentication). + + + The private key is never transmitted to Azure, and it is only used to sign the client assertion used to authenticate with Azure. + + + ![Connect via Azure Certificate](/images/app-connections/azure/client-secrets/create-certificate-method.png) + +
diff --git a/docs/integrations/platforms/infisical-agent.mdx b/docs/integrations/platforms/infisical-agent.mdx index a93bfcdf0a..43d322faac 100644 --- a/docs/integrations/platforms/infisical-agent.mdx +++ b/docs/integrations/platforms/infisical-agent.mdx @@ -48,6 +48,8 @@ While specifying an authentication method is mandatory to start the agent, confi | Field | Description | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `infisical.address` | The URL of the Infisical service. Default: `"https://app.infisical.com"`. | +| `infisical.exit-after-auth` | Whether to exit the agent after authentication and first secret render. Default: `"false"`. | +| `infisical.revoke-credentials-on-shutdown` | Whether to revoke all managed dynamic secret leases and identity access tokens on shutdown. Default: `"false"`. | | `auth.type` | The type of authentication method used. Available options: `universal-auth`, `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, `aws-iam` | | `auth.config.identity-id` | The file path where the machine identity id is stored

This field is required when using any of the following auth types: `kubernetes`, `azure`, `gcp-id-token`, `gcp-iam`, or `aws-iam`. | | `auth.config.service-account-token` | Path to the Kubernetes service account token to use (optional)

Default: `/var/run/secrets/kubernetes.io/serviceaccount/token` | @@ -58,7 +60,7 @@ While specifying an authentication method is mandatory to start the agent, confi | `sinks[].type` | The type of sink in a list of sinks. Each item specifies a sink type. Currently, only `"file"` type is available. | | `sinks[].config.path` | The file path where the access token should be stored for each sink in the list. | | `templates[].source-path` | The path to the template file that should be used to render secrets. | -| `templates[].template-content` | The inline secret template to be used for rendering the secrets. | +| `templates[].template-content` | The inline secret template to be used for rendering the secrets. | | `templates[].destination-path` | The path where the rendered secrets from the source template will be saved to. | | `templates[].config.polling-interval` | How frequently to check for secret changes. Default: `5 minutes` (optional) | | `templates[].config.execute.command` | The command to execute when secret change is detected (optional) | diff --git a/docs/integrations/platforms/kubernetes-injector.mdx b/docs/integrations/platforms/kubernetes-injector.mdx index c8fabbb345..9903dcbc32 100644 --- a/docs/integrations/platforms/kubernetes-injector.mdx +++ b/docs/integrations/platforms/kubernetes-injector.mdx @@ -145,6 +145,17 @@ The entire config needs to be of string format and needs to be assigned to the ` The address of your Infisical instance. This field is optional and will default to `https://app.infisical.com` if not provided. + + Whether to revoke all managed dynamic secret leases and identity access tokens on shutdown. Default: `"false"`. + + If this is set to `true`, all managed dynamic secret leases and identity access tokens will be revoked when a `SIGTERM` signal is sent to the agents container _(such as when a pod is terminated or when the pod is restarted)_. + **Note:** In disaster events such as cluster power outages, a `SIGTERM` signal won't be sent to the agents container, and the credentials will not be revoked. + + + Note that this is currently unsupported on Windows-based pods, and will only work when injecting into Linux-based pods. + + + The authentication type to use to connect to Infisical. Currently only the `kubernetes` authentication type is supported. You can refer to our [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) documentation for more information on how to create a machine identity for Kubernetes Auth. diff --git a/frontend/src/components/v2/SecretInput/SecretInput.tsx b/frontend/src/components/v2/SecretInput/SecretInput.tsx index 93077c07f5..27f0617ed7 100644 --- a/frontend/src/components/v2/SecretInput/SecretInput.tsx +++ b/frontend/src/components/v2/SecretInput/SecretInput.tsx @@ -12,12 +12,14 @@ const syntaxHighlight = ( isVisible?: boolean, isImport?: boolean, isLoadingValue?: boolean, - isErrorLoadingValue?: boolean + isErrorLoadingValue?: boolean, + placeholder?: string ) => { if (isLoadingValue) return HIDDEN_SECRET_VALUE; if (isErrorLoadingValue) return Error loading secret value.; if (isImport && !content) return "IMPORTED"; + if (placeholder && (content === "" || !content)) return placeholder; if (content === "") return "EMPTY"; if (!content) return "EMPTY"; if (!isVisible) return HIDDEN_SECRET_VALUE; @@ -79,6 +81,7 @@ export const SecretInput = forwardRef( canEditButNotView, isLoadingValue, isErrorLoadingValue, + placeholder, ...props }, ref @@ -93,18 +96,25 @@ export const SecretInput = forwardRef(
             
-              
+              
                 {syntaxHighlight(
                   value,
                   isVisible || (isSecretFocused && !valueAlwaysHidden),
                   isImport,
                   isLoadingValue,
-                  isErrorLoadingValue
+                  isErrorLoadingValue,
+                  placeholder
                 )}