mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Extend posture checks with peer network range check (#344)
add support to peer network checks
This commit is contained in:
1
.github/workflows/build_and_push.yml
vendored
1
.github/workflows/build_and_push.yml
vendored
@@ -2,6 +2,7 @@ name: build and push
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- "feature/**"
|
||||||
- main
|
- main
|
||||||
tags:
|
tags:
|
||||||
- "**"
|
- "**"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface PostureCheck {
|
|||||||
nb_version_check?: NetBirdVersionCheck;
|
nb_version_check?: NetBirdVersionCheck;
|
||||||
os_version_check?: OperatingSystemVersionCheck;
|
os_version_check?: OperatingSystemVersionCheck;
|
||||||
geo_location_check?: GeoLocationCheck;
|
geo_location_check?: GeoLocationCheck;
|
||||||
|
peer_network_range_check?: PeerNetworkRangeCheck;
|
||||||
};
|
};
|
||||||
policies?: Policy[];
|
policies?: Policy[];
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
@@ -47,6 +48,11 @@ export interface GeoLocation {
|
|||||||
city_name: string;
|
city_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PeerNetworkRangeCheck {
|
||||||
|
ranges: string[];
|
||||||
|
action: "allow" | "deny";
|
||||||
|
}
|
||||||
|
|
||||||
export const windowsKernelVersions: SelectOption[] = [
|
export const windowsKernelVersions: SelectOption[] = [
|
||||||
{ value: "5.0", label: "Windows 2000" },
|
{ value: "5.0", label: "Windows 2000" },
|
||||||
{ value: "5.1", label: "Windows XP" },
|
{ value: "5.1", label: "Windows XP" },
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<PostureCheckCard
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
key={open ? 1 : 0}
|
||||||
|
icon={<NetworkIcon size={16} />}
|
||||||
|
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)}
|
||||||
|
>
|
||||||
|
<CheckContent
|
||||||
|
value={value}
|
||||||
|
onChange={(v) => {
|
||||||
|
onChange(v);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PostureCheckCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface NetworkRange {
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheckContent = ({ value, onChange }: Props) => {
|
||||||
|
const [allowOrDeny, setAllowOrDeny] = useState<string>(
|
||||||
|
value?.action ? value.action : "allow",
|
||||||
|
);
|
||||||
|
|
||||||
|
const [networkRanges, setNetworkRanges] = useState<NetworkRange[]>(
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div className={"flex flex-col px-8 gap-2 pb-6"}>
|
||||||
|
<div className={"flex justify-between items-start gap-10 mt-2"}>
|
||||||
|
<div>
|
||||||
|
<Label>Allow or Block Ranges</Label>
|
||||||
|
<HelpText className={""}>
|
||||||
|
Choose whether you want to allow or block specific peer network
|
||||||
|
ranges
|
||||||
|
</HelpText>
|
||||||
|
</div>
|
||||||
|
<RadioGroup value={allowOrDeny} onChange={setAllowOrDeny}>
|
||||||
|
<RadioGroupItem value={"allow"} variant={"green"}>
|
||||||
|
<ShieldCheck size={16} />
|
||||||
|
Allow
|
||||||
|
</RadioGroupItem>
|
||||||
|
<RadioGroupItem value={"deny"} variant={"red"}>
|
||||||
|
<ShieldXIcon size={16} />
|
||||||
|
Block
|
||||||
|
</RadioGroupItem>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
{networkRanges.length > 0 && (
|
||||||
|
<div className={"mb-2 flex flex-col gap-2 w-full "}>
|
||||||
|
{networkRanges.map((ipRange) => {
|
||||||
|
return (
|
||||||
|
<div key={ipRange.id} className={"flex gap-2"}>
|
||||||
|
<div className={"w-full"}>
|
||||||
|
<Input
|
||||||
|
customPrefix={<NetworkIcon size={16} />}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={"h-[42px]"}
|
||||||
|
variant={"default-outline"}
|
||||||
|
onClick={() => removeNetworkRange(ipRange.id)}
|
||||||
|
>
|
||||||
|
<MinusCircleIcon size={15} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button variant={"dotted"} size={"sm"} onClick={addNetworkRange}>
|
||||||
|
<PlusCircle size={16} />
|
||||||
|
Add Network Range
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ModalFooter className={"items-center"}>
|
||||||
|
<div className={"w-full"}>
|
||||||
|
<Paragraph className={"text-sm mt-auto"}>
|
||||||
|
Learn more about
|
||||||
|
<InlineLink
|
||||||
|
href={
|
||||||
|
"https://docs.netbird.io/how-to/manage-posture-checks#peer-network-range-check"
|
||||||
|
}
|
||||||
|
target={"_blank"}
|
||||||
|
>
|
||||||
|
Peer Network Range Check
|
||||||
|
<ExternalLinkIcon size={12} />
|
||||||
|
</InlineLink>
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
<div className={"flex gap-3 w-full justify-end"}>
|
||||||
|
<ModalClose asChild={true}>
|
||||||
|
<Button variant={"secondary"}>Cancel</Button>
|
||||||
|
</ModalClose>
|
||||||
|
<Button
|
||||||
|
variant={"primary"}
|
||||||
|
disabled={hasErrorsOrIsEmpty}
|
||||||
|
onClick={() => {
|
||||||
|
if (isEmpty(networkRanges)) {
|
||||||
|
onChange(undefined);
|
||||||
|
} else {
|
||||||
|
onChange({
|
||||||
|
action: allowOrDeny as "allow" | "deny",
|
||||||
|
ranges: networkRanges
|
||||||
|
.map((r) => r.value)
|
||||||
|
.filter((r) => r !== ""),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 ? (
|
||||||
|
<FullTooltip
|
||||||
|
className={"w-full"}
|
||||||
|
interactive={true}
|
||||||
|
contentClassName={"p-0"}
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className={"text-neutral-300 text-sm max-w-xs flex flex-col gap-1"}
|
||||||
|
>
|
||||||
|
<div className={"px-4 pt-3"}>
|
||||||
|
{check.action == "allow" ? (
|
||||||
|
<span>
|
||||||
|
<span className={"text-green-500 font-semibold"}>
|
||||||
|
Allow only
|
||||||
|
</span>{" "}
|
||||||
|
the following peer network ranges
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
<span className={"text-red-500 font-semibold"}>Block</span> the
|
||||||
|
following peer network ranges
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ScrollArea
|
||||||
|
className={"max-h-[275px] overflow-y-auto flex flex-col px-4"}
|
||||||
|
>
|
||||||
|
<div className={"flex flex-col gap-1.5 mt-1 text-xs mb-3.5"}>
|
||||||
|
{check.ranges.map((ipRange, index) => {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
variant={"gray"}
|
||||||
|
useHover={false}
|
||||||
|
key={index}
|
||||||
|
className={
|
||||||
|
"justify-start font-medium font-mono text-[11px]"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<NetworkIcon size={10} />
|
||||||
|
{ipRange}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</FullTooltip>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
import { PostureCheckGeoLocation } from "@/modules/posture-checks/checks/PostureCheckGeoLocation";
|
import { PostureCheckGeoLocation } from "@/modules/posture-checks/checks/PostureCheckGeoLocation";
|
||||||
import { PostureCheckNetBirdVersion } from "@/modules/posture-checks/checks/PostureCheckNetBirdVersion";
|
import { PostureCheckNetBirdVersion } from "@/modules/posture-checks/checks/PostureCheckNetBirdVersion";
|
||||||
import { PostureCheckOperatingSystem } from "@/modules/posture-checks/checks/PostureCheckOperatingSystem";
|
import { PostureCheckOperatingSystem } from "@/modules/posture-checks/checks/PostureCheckOperatingSystem";
|
||||||
|
import { PostureCheckPeerNetworkRange } from "@/modules/posture-checks/checks/PostureCheckPeerNetworkRange";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -54,6 +55,9 @@ export default function PostureCheckModal({
|
|||||||
const [osVersionCheck, setOsVersionCheck] = useState(
|
const [osVersionCheck, setOsVersionCheck] = useState(
|
||||||
postureCheck?.checks.os_version_check || undefined,
|
postureCheck?.checks.os_version_check || undefined,
|
||||||
);
|
);
|
||||||
|
const [peerNetworkRangeCheck, setPeerNetworkRangeCheck] = useState(
|
||||||
|
postureCheck?.checks.peer_network_range_check || undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const validateOSCheck = (osCheck?: OperatingSystemVersionCheck) => {
|
const validateOSCheck = (osCheck?: OperatingSystemVersionCheck) => {
|
||||||
if (!osCheck) return;
|
if (!osCheck) return;
|
||||||
@@ -93,6 +97,7 @@ export default function PostureCheckModal({
|
|||||||
nb_version_check: nbVersionCheck,
|
nb_version_check: nbVersionCheck,
|
||||||
geo_location_check: validateLocationCheck(geoLocationCheck),
|
geo_location_check: validateLocationCheck(geoLocationCheck),
|
||||||
os_version_check: validateOSCheck(osVersionCheck),
|
os_version_check: validateOSCheck(osVersionCheck),
|
||||||
|
peer_network_range_check: peerNetworkRangeCheck,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,7 +130,10 @@ export default function PostureCheckModal({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isAtLeastOneCheckEnabled =
|
const isAtLeastOneCheckEnabled =
|
||||||
!!nbVersionCheck || !!geoLocationCheck || !!osVersionCheck;
|
!!nbVersionCheck ||
|
||||||
|
!!geoLocationCheck ||
|
||||||
|
!!osVersionCheck ||
|
||||||
|
!!peerNetworkRangeCheck;
|
||||||
const canCreate = !isEmpty(name) && isAtLeastOneCheckEnabled;
|
const canCreate = !isEmpty(name) && isAtLeastOneCheckEnabled;
|
||||||
|
|
||||||
const [tab, setTab] = useState("checks");
|
const [tab, setTab] = useState("checks");
|
||||||
@@ -180,6 +188,10 @@ export default function PostureCheckModal({
|
|||||||
value={osVersionCheck}
|
value={osVersionCheck}
|
||||||
onChange={setOsVersionCheck}
|
onChange={setOsVersionCheck}
|
||||||
/>
|
/>
|
||||||
|
<PostureCheckPeerNetworkRange
|
||||||
|
value={peerNetworkRangeCheck}
|
||||||
|
onChange={setPeerNetworkRangeCheck}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value={"general"} className={"pb-8 px-8"}>
|
<TabsContent value={"general"} className={"pb-8 px-8"}>
|
||||||
@@ -221,9 +233,7 @@ export default function PostureCheckModal({
|
|||||||
<Paragraph className={"text-sm mt-auto"}>
|
<Paragraph className={"text-sm mt-auto"}>
|
||||||
Learn more about
|
Learn more about
|
||||||
<InlineLink
|
<InlineLink
|
||||||
href={
|
href={"https://docs.netbird.io/how-to/manage-posture-checks"}
|
||||||
"https://docs.netbird.io/how-to/manage-posture-checks"
|
|
||||||
}
|
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
>
|
>
|
||||||
Posture Checks
|
Posture Checks
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { cn } from "@utils/helpers";
|
import { cn } from "@utils/helpers";
|
||||||
import { Disc3Icon, FlagIcon } from "lucide-react";
|
import { Disc3Icon, FlagIcon, NetworkIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
|
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
|
||||||
import { PostureCheck } from "@/interfaces/PostureCheck";
|
import { PostureCheck } from "@/interfaces/PostureCheck";
|
||||||
import { GeoLocationTooltip } from "@/modules/posture-checks/checks/tooltips/GeoLocationTooltip";
|
import { GeoLocationTooltip } from "@/modules/posture-checks/checks/tooltips/GeoLocationTooltip";
|
||||||
import { NetBirdVersionTooltip } from "@/modules/posture-checks/checks/tooltips/NetBirdVersionTooltip";
|
import { NetBirdVersionTooltip } from "@/modules/posture-checks/checks/tooltips/NetBirdVersionTooltip";
|
||||||
import { OperatingSystemTooltip } from "@/modules/posture-checks/checks/tooltips/OperatingSystemTooltip";
|
import { OperatingSystemTooltip } from "@/modules/posture-checks/checks/tooltips/OperatingSystemTooltip";
|
||||||
|
import { PeerNetworkRangeTooltip } from "@/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
check: PostureCheck;
|
check: PostureCheck;
|
||||||
@@ -56,6 +57,20 @@ export const PostureCheckChecksCell = ({ check }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</OperatingSystemTooltip>
|
</OperatingSystemTooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{check.checks.peer_network_range_check && (
|
||||||
|
<PeerNetworkRangeTooltip
|
||||||
|
check={check.checks.peer_network_range_check}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"bg-gradient-to-tr from-blue-500 to-blue-400 h-8 w-8 rounded-full flex items-center justify-center relative z-[8] hover:scale-[1.1] transition-all",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<NetworkIcon size={14} />
|
||||||
|
</div>
|
||||||
|
</PeerNetworkRangeTooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user