-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extend posture checks with peer network range check (#344)
add support to peer network checks
- Loading branch information
Showing
6 changed files
with
323 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ name: build and push | |
on: | ||
push: | ||
branches: | ||
- "feature/**" | ||
- main | ||
tags: | ||
- "**" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
219 changes: 219 additions & 0 deletions
219
src/modules/posture-checks/checks/PostureCheckPeerNetworkRange.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
</> | ||
); | ||
}; |
67 changes: 67 additions & 0 deletions
67
src/modules/posture-checks/checks/tooltips/PeerNetworkRangeTooltip.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.