diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index 1d45a73..2eff4e0 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -2,6 +2,7 @@ name: build and push on: push: branches: + - "feature/**" - main tags: - "**" diff --git a/src/interfaces/PostureCheck.ts b/src/interfaces/PostureCheck.ts index 3128bdd..ad11098 100644 --- a/src/interfaces/PostureCheck.ts +++ b/src/interfaces/PostureCheck.ts @@ -9,6 +9,7 @@ export interface PostureCheck { nb_version_check?: NetBirdVersionCheck; os_version_check?: OperatingSystemVersionCheck; geo_location_check?: GeoLocationCheck; + peer_network_range_check?: PeerNetworkRangeCheck; }; policies?: Policy[]; active?: boolean; @@ -47,6 +48,11 @@ export interface GeoLocation { city_name: string; } +export interface PeerNetworkRangeCheck { + ranges: string[]; + action: "allow" | "deny"; +} + export const windowsKernelVersions: SelectOption[] = [ { value: "5.0", label: "Windows 2000" }, { value: "5.1", label: "Windows XP" }, diff --git a/src/modules/posture-checks/checks/PostureCheckPeerNetworkRange.tsx b/src/modules/posture-checks/checks/PostureCheckPeerNetworkRange.tsx new file mode 100644 index 0000000..b4d8a19 --- /dev/null +++ b/src/modules/posture-checks/checks/PostureCheckPeerNetworkRange.tsx @@ -0,0 +1,219 @@ +import Button from "@components/Button"; +import HelpText from "@components/HelpText"; +import InlineLink from "@components/InlineLink"; +import { Input } from "@components/Input"; +import { Label } from "@components/Label"; +import { ModalClose, ModalFooter } from "@components/modal/Modal"; +import Paragraph from "@components/Paragraph"; +import { RadioGroup, RadioGroupItem } from "@components/RadioGroup"; +import cidr from "ip-cidr"; +import { isEmpty, uniqueId } from "lodash"; +import { + ExternalLinkIcon, + MinusCircleIcon, + NetworkIcon, + PlusCircle, + ShieldCheck, + ShieldXIcon, +} from "lucide-react"; +import * as React from "react"; +import { useMemo, useState } from "react"; +import { PeerNetworkRangeCheck } from "@/interfaces/PostureCheck"; +import { PostureCheckCard } from "@/modules/posture-checks/ui/PostureCheckCard"; + +type Props = { + value?: PeerNetworkRangeCheck; + onChange: (value: PeerNetworkRangeCheck | undefined) => void; +}; + +export const PostureCheckPeerNetworkRange = ({ value, onChange }: Props) => { + const [open, setOpen] = useState(false); + + return ( + } + title={"Peer Network Range"} + modalWidthClass={"max-w-xl"} + description={ + "Restrict access by allowing or blocking peer network ranges." + } + iconClass={"bg-gradient-to-tr from-blue-500 to-blue-400"} + active={value !== undefined} + onReset={() => onChange(undefined)} + > + { + onChange(v); + setOpen(false); + }} + /> + + ); +}; + +interface NetworkRange { + id: string; + value: string; +} + +const CheckContent = ({ value, onChange }: Props) => { + const [allowOrDeny, setAllowOrDeny] = useState( + value?.action ? value.action : "allow", + ); + + const [networkRanges, setNetworkRanges] = useState( + value?.ranges + ? value.ranges.map((r) => { + return { + id: uniqueId("range"), + value: r, + }; + }) + : [], + ); + + const handleNetworkRangeChange = (id: string, value: string) => { + const newRanges = networkRanges.map((r) => + r.id === id ? { ...r, value } : r, + ); + setNetworkRanges(newRanges); + }; + + const removeNetworkRange = (id: string) => { + const newRanges = networkRanges.filter((r) => r.id !== id); + setNetworkRanges(newRanges); + }; + + const addNetworkRange = () => { + setNetworkRanges([...networkRanges, { id: uniqueId("range"), value: "" }]); + }; + + const validateNetworkRange = (networkRange: string) => { + if (networkRange == "") return ""; + const validCIDR = cidr.isValidAddress(networkRange); + if (!validCIDR) return "Please enter a valid CIDR, e.g., 192.168.1.0/24"; + return ""; + }; + + const cidrErrors = useMemo(() => { + if (networkRanges && networkRanges.length > 0) { + return networkRanges.map((r) => { + return { + id: r.id, + error: validateNetworkRange(r.value), + }; + }); + } else { + return []; + } + }, [networkRanges]); + + const hasErrorsOrIsEmpty = useMemo(() => { + if (networkRanges.length === 0) return true; + return cidrErrors.some((e) => e.error !== ""); + }, [networkRanges, cidrErrors]); + + return ( + <> +
+
+
+ + + Choose whether you want to allow or block specific peer network + ranges + +
+ + + + Allow + + + + Block + + +
+ {networkRanges.length > 0 && ( +
+ {networkRanges.map((ipRange) => { + return ( +
+
+ } + placeholder={"e.g., 172.16.0.0/16"} + value={ipRange.value} + error={cidrErrors.find((e) => e.id === ipRange.id)?.error} + errorTooltip={false} + className={"font-mono !text-[13px] w-full"} + onChange={(e) => + handleNetworkRangeChange(ipRange.id, e.target.value) + } + /> +
+ + +
+ ); + })} +
+ )} + +
+ +
+ + Learn more about + + Peer Network Range Check + + + +
+
+ + + + +
+
+ + ); +}; diff --git a/src/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip.tsx b/src/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip.tsx new file mode 100644 index 0000000..0cc7937 --- /dev/null +++ b/src/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip.tsx @@ -0,0 +1,67 @@ +import Badge from "@components/Badge"; +import FullTooltip from "@components/FullTooltip"; +import { ScrollArea } from "@components/ScrollArea"; +import { NetworkIcon } from "lucide-react"; +import * as React from "react"; +import { PeerNetworkRangeCheck } from "@/interfaces/PostureCheck"; + +type Props = { + check?: PeerNetworkRangeCheck; + children?: React.ReactNode; +}; +export const PeerNetworkRangeTooltip = ({ check, children }: Props) => { + return check ? ( + +
+ {check.action == "allow" ? ( + + + Allow only + {" "} + the following peer network ranges + + ) : ( + + Block the + following peer network ranges + + )} +
+ + +
+ {check.ranges.map((ipRange, index) => { + return ( + + + {ipRange} + + ); + })} +
+
+ + } + > + {children} +
+ ) : ( + children + ); +}; diff --git a/src/modules/posture-checks/modal/PostureCheckModal.tsx b/src/modules/posture-checks/modal/PostureCheckModal.tsx index bf7cf58..a0db0d8 100644 --- a/src/modules/posture-checks/modal/PostureCheckModal.tsx +++ b/src/modules/posture-checks/modal/PostureCheckModal.tsx @@ -23,6 +23,7 @@ import { import { PostureCheckGeoLocation } from "@/modules/posture-checks/checks/PostureCheckGeoLocation"; import { PostureCheckNetBirdVersion } from "@/modules/posture-checks/checks/PostureCheckNetBirdVersion"; import { PostureCheckOperatingSystem } from "@/modules/posture-checks/checks/PostureCheckOperatingSystem"; +import { PostureCheckPeerNetworkRange } from "@/modules/posture-checks/checks/PostureCheckPeerNetworkRange"; type Props = { open: boolean; @@ -54,6 +55,9 @@ export default function PostureCheckModal({ const [osVersionCheck, setOsVersionCheck] = useState( postureCheck?.checks.os_version_check || undefined, ); + const [peerNetworkRangeCheck, setPeerNetworkRangeCheck] = useState( + postureCheck?.checks.peer_network_range_check || undefined, + ); const validateOSCheck = (osCheck?: OperatingSystemVersionCheck) => { if (!osCheck) return; @@ -93,6 +97,7 @@ export default function PostureCheckModal({ nb_version_check: nbVersionCheck, geo_location_check: validateLocationCheck(geoLocationCheck), os_version_check: validateOSCheck(osVersionCheck), + peer_network_range_check: peerNetworkRangeCheck, }, }; @@ -125,7 +130,10 @@ export default function PostureCheckModal({ }; const isAtLeastOneCheckEnabled = - !!nbVersionCheck || !!geoLocationCheck || !!osVersionCheck; + !!nbVersionCheck || + !!geoLocationCheck || + !!osVersionCheck || + !!peerNetworkRangeCheck; const canCreate = !isEmpty(name) && isAtLeastOneCheckEnabled; const [tab, setTab] = useState("checks"); @@ -180,6 +188,10 @@ export default function PostureCheckModal({ value={osVersionCheck} onChange={setOsVersionCheck} /> + @@ -221,9 +233,7 @@ export default function PostureCheckModal({ Learn more about Posture Checks diff --git a/src/modules/posture-checks/table/cells/PostureCheckChecksCell.tsx b/src/modules/posture-checks/table/cells/PostureCheckChecksCell.tsx index 7c4d348..19a58fb 100644 --- a/src/modules/posture-checks/table/cells/PostureCheckChecksCell.tsx +++ b/src/modules/posture-checks/table/cells/PostureCheckChecksCell.tsx @@ -1,11 +1,12 @@ import { cn } from "@utils/helpers"; -import { Disc3Icon, FlagIcon } from "lucide-react"; +import { Disc3Icon, FlagIcon, NetworkIcon } from "lucide-react"; import * as React from "react"; import NetBirdIcon from "@/assets/icons/NetBirdIcon"; import { PostureCheck } from "@/interfaces/PostureCheck"; import { GeoLocationTooltip } from "@/modules/posture-checks/checks/tooltips/GeoLocationTooltip"; import { NetBirdVersionTooltip } from "@/modules/posture-checks/checks/tooltips/NetBirdVersionTooltip"; import { OperatingSystemTooltip } from "@/modules/posture-checks/checks/tooltips/OperatingSystemTooltip"; +import { PeerNetworkRangeTooltip } from "@/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip"; type Props = { check: PostureCheck; @@ -56,6 +57,20 @@ export const PostureCheckChecksCell = ({ check }: Props) => { )} + + {check.checks.peer_network_range_check && ( + +
+ +
+
+ )}