From e8ee2d109334b650b06b5b9108b965efbe69b33c Mon Sep 17 00:00:00 2001 From: pascal Date: Fri, 4 Sep 2026 20:17:51 +0200 Subject: [PATCH 1/2] add certificate posturecheck support --- src/interfaces/PostureCheck.ts | 5 + .../checks/PostureCheckCertificate.tsx | 212 ++++++++++++++++++ .../checks/tooltips/CertificateTooltip.tsx | 73 ++++++ .../helper/CertificateHelper.ts | 54 +++++ .../modal/PostureCheckModal.tsx | 16 +- .../table/cells/PostureCheckChecksCell.tsx | 21 +- src/modules/posture-checks/usePostureCheck.ts | 12 +- 7 files changed, 390 insertions(+), 3 deletions(-) create mode 100644 src/modules/posture-checks/checks/PostureCheckCertificate.tsx create mode 100644 src/modules/posture-checks/checks/tooltips/CertificateTooltip.tsx create mode 100644 src/modules/posture-checks/helper/CertificateHelper.ts diff --git a/src/interfaces/PostureCheck.ts b/src/interfaces/PostureCheck.ts index e67d7b741..8a1d95be3 100644 --- a/src/interfaces/PostureCheck.ts +++ b/src/interfaces/PostureCheck.ts @@ -11,6 +11,7 @@ export interface PostureCheck { geo_location_check?: GeoLocationCheck; peer_network_range_check?: PeerNetworkRangeCheck; process_check?: ProcessCheck; + certificate_check?: CertificateCheck; }; policies?: Policy[]; active?: boolean; @@ -65,6 +66,10 @@ export interface Process { windows_path?: string; } +export interface CertificateCheck { + ca_certificates: string[]; +} + export const windowsKernelVersions: SelectOption[] = [ { value: "10.0", label: "Windows 10" }, { value: "10.0.2", label: "Windows 11" }, diff --git a/src/modules/posture-checks/checks/PostureCheckCertificate.tsx b/src/modules/posture-checks/checks/PostureCheckCertificate.tsx new file mode 100644 index 000000000..1da6b2ba8 --- /dev/null +++ b/src/modules/posture-checks/checks/PostureCheckCertificate.tsx @@ -0,0 +1,212 @@ +import Button from "@components/Button"; +import HelpText from "@components/HelpText"; +import InlineLink from "@components/InlineLink"; +import { Label } from "@components/Label"; +import { ModalClose, ModalFooter } from "@components/modal/Modal"; +import Paragraph from "@components/Paragraph"; +import { Textarea } from "@components/Textarea"; +import { uniqueId } from "lodash"; +import { + ExternalLinkIcon, + FileKey2Icon, + MinusCircleIcon, + PlusCircle, +} from "lucide-react"; +import * as React from "react"; +import { useMemo, useState } from "react"; +import { CertificateCheck } from "@/interfaces/PostureCheck"; +import { + isValidPEMCertificate, + useCertificateFingerprints, +} from "@/modules/posture-checks/helper/CertificateHelper"; +import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard"; + +type Props = { + value?: CertificateCheck; + onChange: (value: CertificateCheck | undefined) => void; + disabled?: boolean; +}; + +export const PostureCheckCertificate = ({ + value, + onChange, + disabled, +}: Props) => { + const [open, setOpen] = useState(false); + + return ( + 0} + title={"Certificate"} + description={ + "Restrict access to peers holding a certificate issued by your own certificate authority." + } + icon={} + iconClass={"bg-gradient-to-tr from-purple-500 to-purple-400"} + modalWidthClass={"max-w-2xl"} + onReset={() => onChange(undefined)} + > + { + onChange(v); + setOpen(false); + }} + disabled={disabled} + /> + + ); +}; + +type CACertificate = { + id: string; + pem: string; +}; + +const newCertificate = (pem = ""): CACertificate => ({ + id: uniqueId("ca-certificate"), + pem, +}); + +const rowsFor = (pem: string) => + Math.min(Math.max(pem.split("\n").length + 1, 6), 32); + +const CheckContent = ({ value, onChange, disabled }: Props) => { + const [certificates, setCertificates] = useState( + value?.ca_certificates?.length + ? value.ca_certificates.map((pem) => newCertificate(pem)) + : [newCertificate()], + ); + + const pems = useMemo(() => certificates.map((c) => c.pem), [certificates]); + const fingerprints = useCertificateFingerprints(pems); + + const errors = useMemo( + () => + certificates.map((c) => + c.pem.trim() !== "" && !isValidPEMCertificate(c.pem) + ? "Please paste a valid PEM encoded certificate" + : "", + ), + [certificates], + ); + + const hasErrorsOrIsEmpty = + certificates.length === 0 || + certificates.some((c) => c.pem.trim() === "") || + errors.some((e) => e !== ""); + + const updateCertificate = (id: string, pem: string) => { + setCertificates(certificates.map((c) => (c.id === id ? { ...c, pem } : c))); + }; + + const removeCertificate = (id: string) => { + setCertificates(certificates.filter((c) => c.id !== id)); + }; + + return ( + <> +
+
+
+ + + Paste the PEM encoded public certificate of the root or + intermediate CA that issues your device certificates. Peers will + only be allowed to connect if they hold a certificate issued by + one of these CAs and prove possession of its private key. + +
+
+ {certificates.length > 0 && ( +
+ {certificates.map((c, index) => { + return ( +
+
+