mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0f4a3564a |
No files matched your search
@@ -33,12 +33,21 @@ export const CrowdSecMode = {
|
|||||||
|
|
||||||
export type CrowdSecMode = (typeof CrowdSecMode)[keyof typeof CrowdSecMode];
|
export type CrowdSecMode = (typeof CrowdSecMode)[keyof typeof CrowdSecMode];
|
||||||
|
|
||||||
|
export const AppSecMode = {
|
||||||
|
OFF: "off",
|
||||||
|
ENFORCE: "enforce",
|
||||||
|
OBSERVE: "observe",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AppSecMode = (typeof AppSecMode)[keyof typeof AppSecMode];
|
||||||
|
|
||||||
export interface AccessRestrictions {
|
export interface AccessRestrictions {
|
||||||
allowed_cidrs?: string[];
|
allowed_cidrs?: string[];
|
||||||
blocked_cidrs?: string[];
|
blocked_cidrs?: string[];
|
||||||
allowed_countries?: string[];
|
allowed_countries?: string[];
|
||||||
blocked_countries?: string[];
|
blocked_countries?: string[];
|
||||||
crowdsec_mode?: CrowdSecMode;
|
crowdsec_mode?: CrowdSecMode;
|
||||||
|
appsec_mode?: AppSecMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReverseProxyMeta {
|
export interface ReverseProxyMeta {
|
||||||
@@ -122,6 +131,7 @@ export interface ReverseProxyDomain {
|
|||||||
supports_custom_ports?: boolean;
|
supports_custom_ports?: boolean;
|
||||||
require_subdomain?: boolean;
|
require_subdomain?: boolean;
|
||||||
supports_crowdsec?: boolean;
|
supports_crowdsec?: boolean;
|
||||||
|
supports_appsec?: boolean;
|
||||||
supports_private?: boolean;
|
supports_private?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +198,7 @@ export interface ReverseProxyCluster {
|
|||||||
supports_custom_ports?: boolean;
|
supports_custom_ports?: boolean;
|
||||||
require_subdomain?: boolean;
|
require_subdomain?: boolean;
|
||||||
supports_crowdsec?: boolean;
|
supports_crowdsec?: boolean;
|
||||||
|
supports_appsec?: boolean;
|
||||||
// True when at least one connected proxy in this cluster is running embedded
|
// True when at least one connected proxy in this cluster is running embedded
|
||||||
// in a netbird client (`netbird proxy`) and serving over a WireGuard tunnel.
|
// in a netbird client (`netbird proxy`) and serving over a WireGuard tunnel.
|
||||||
// Lets the dashboard distinguish per-peer / private clusters from centralised
|
// Lets the dashboard distinguish per-peer / private clusters from centralised
|
||||||
|
|||||||
@@ -19,8 +19,13 @@ import {
|
|||||||
SelectOption,
|
SelectOption,
|
||||||
} from "@components/select/SelectDropdown";
|
} from "@components/select/SelectDropdown";
|
||||||
import { CountrySelector } from "@/components/ui/CountrySelector";
|
import { CountrySelector } from "@/components/ui/CountrySelector";
|
||||||
import { AccessRestrictions, CrowdSecMode } from "@/interfaces/ReverseProxy";
|
import {
|
||||||
|
AccessRestrictions,
|
||||||
|
AppSecMode,
|
||||||
|
CrowdSecMode,
|
||||||
|
} from "@/interfaces/ReverseProxy";
|
||||||
import { ReverseProxyCrowdSecIPReputation } from "@/modules/reverse-proxy/ReverseProxyCrowdSecIPReputation";
|
import { ReverseProxyCrowdSecIPReputation } from "@/modules/reverse-proxy/ReverseProxyCrowdSecIPReputation";
|
||||||
|
import { ReverseProxyAppSecInspection } from "@/modules/reverse-proxy/ReverseProxyAppSecInspection";
|
||||||
|
|
||||||
type AccessAction = "allow" | "block";
|
type AccessAction = "allow" | "block";
|
||||||
type AccessRuleType = "country" | "ip" | "cidr";
|
type AccessRuleType = "country" | "ip" | "cidr";
|
||||||
@@ -121,6 +126,7 @@ function restrictionsToRules(
|
|||||||
function rulesToRestrictions(
|
function rulesToRestrictions(
|
||||||
rules: AccessRule[],
|
rules: AccessRule[],
|
||||||
crowdsecMode?: CrowdSecMode,
|
crowdsecMode?: CrowdSecMode,
|
||||||
|
appsecMode?: AppSecMode,
|
||||||
): AccessRestrictions | undefined {
|
): AccessRestrictions | undefined {
|
||||||
const allowed_countries: string[] = [];
|
const allowed_countries: string[] = [];
|
||||||
const blocked_countries: string[] = [];
|
const blocked_countries: string[] = [];
|
||||||
@@ -144,12 +150,14 @@ function rulesToRestrictions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasCrowdSec = crowdsecMode != null && crowdsecMode !== CrowdSecMode.OFF;
|
const hasCrowdSec = crowdsecMode != null && crowdsecMode !== CrowdSecMode.OFF;
|
||||||
|
const hasAppSec = appsecMode != null && appsecMode !== AppSecMode.OFF;
|
||||||
const hasAny =
|
const hasAny =
|
||||||
allowed_countries.length > 0 ||
|
allowed_countries.length > 0 ||
|
||||||
blocked_countries.length > 0 ||
|
blocked_countries.length > 0 ||
|
||||||
allowed_cidrs.length > 0 ||
|
allowed_cidrs.length > 0 ||
|
||||||
blocked_cidrs.length > 0 ||
|
blocked_cidrs.length > 0 ||
|
||||||
hasCrowdSec;
|
hasCrowdSec ||
|
||||||
|
hasAppSec;
|
||||||
|
|
||||||
if (!hasAny) return undefined;
|
if (!hasAny) return undefined;
|
||||||
|
|
||||||
@@ -159,6 +167,7 @@ function rulesToRestrictions(
|
|||||||
...(allowed_cidrs.length > 0 && { allowed_cidrs }),
|
...(allowed_cidrs.length > 0 && { allowed_cidrs }),
|
||||||
...(blocked_cidrs.length > 0 && { blocked_cidrs }),
|
...(blocked_cidrs.length > 0 && { blocked_cidrs }),
|
||||||
...(hasCrowdSec && { crowdsec_mode: crowdsecMode }),
|
...(hasCrowdSec && { crowdsec_mode: crowdsecMode }),
|
||||||
|
...(hasAppSec && { appsec_mode: appsecMode }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +176,7 @@ type Props = {
|
|||||||
onChange: (value: AccessRestrictions | undefined) => void;
|
onChange: (value: AccessRestrictions | undefined) => void;
|
||||||
onValidationChange?: (hasErrors: boolean) => void;
|
onValidationChange?: (hasErrors: boolean) => void;
|
||||||
supportsCrowdSec?: boolean;
|
supportsCrowdSec?: boolean;
|
||||||
|
supportsAppSec?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function validateRule(rule: AccessRule): string {
|
function validateRule(rule: AccessRule): string {
|
||||||
@@ -193,6 +203,7 @@ export const ReverseProxyAccessControlRules = ({
|
|||||||
onChange,
|
onChange,
|
||||||
onValidationChange,
|
onValidationChange,
|
||||||
supportsCrowdSec,
|
supportsCrowdSec,
|
||||||
|
supportsAppSec,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [rules, dispatch] = useReducer(
|
const [rules, dispatch] = useReducer(
|
||||||
rulesReducer,
|
rulesReducer,
|
||||||
@@ -200,6 +211,10 @@ export const ReverseProxyAccessControlRules = ({
|
|||||||
restrictionsToRules,
|
restrictionsToRules,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [appsecMode, setAppsecMode] = useState<AppSecMode>(
|
||||||
|
value?.appsec_mode ?? AppSecMode.OFF,
|
||||||
|
);
|
||||||
|
|
||||||
const [crowdsecMode, setCrowdsecMode] = useState<CrowdSecMode>(
|
const [crowdsecMode, setCrowdsecMode] = useState<CrowdSecMode>(
|
||||||
value?.crowdsec_mode ?? CrowdSecMode.OFF,
|
value?.crowdsec_mode ?? CrowdSecMode.OFF,
|
||||||
);
|
);
|
||||||
@@ -227,8 +242,14 @@ export const ReverseProxyAccessControlRules = ({
|
|||||||
}, [supportsCrowdSec]);
|
}, [supportsCrowdSec]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChangeRef.current(rulesToRestrictions(rules, crowdsecMode));
|
if (!supportsAppSec) {
|
||||||
}, [rules, crowdsecMode]);
|
setAppsecMode(AppSecMode.OFF);
|
||||||
|
}
|
||||||
|
}, [supportsAppSec]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChangeRef.current(rulesToRestrictions(rules, crowdsecMode, appsecMode));
|
||||||
|
}, [rules, crowdsecMode, appsecMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onValidationChangeRef.current?.(hasErrors);
|
onValidationChangeRef.current?.(hasErrors);
|
||||||
@@ -242,6 +263,12 @@ export const ReverseProxyAccessControlRules = ({
|
|||||||
onChange={setCrowdsecMode}
|
onChange={setCrowdsecMode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{supportsAppSec && (
|
||||||
|
<ReverseProxyAppSecInspection
|
||||||
|
value={appsecMode}
|
||||||
|
onChange={setAppsecMode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Label>Access Control Rules</Label>
|
<Label>Access Control Rules</Label>
|
||||||
<HelpText>
|
<HelpText>
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Label } from "@components/Label";
|
||||||
|
import HelpText from "@components/HelpText";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@components/Select";
|
||||||
|
import { EyeIcon, PowerOffIcon, ShieldCheckIcon } from "lucide-react";
|
||||||
|
import { HelpTooltip } from "@components/HelpTooltip";
|
||||||
|
import { AppSecMode } from "@/interfaces/ReverseProxy";
|
||||||
|
import Image from "next/image";
|
||||||
|
import CrowdSecIconImage from "@/assets/integrations/crowdsec.png";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: AppSecMode;
|
||||||
|
onChange: (value: AppSecMode) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AppSecOption = {
|
||||||
|
label: string;
|
||||||
|
description?: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const APPSEC_OPTIONS: Record<AppSecMode, AppSecOption> = {
|
||||||
|
[AppSecMode.OFF]: {
|
||||||
|
label: "Disabled",
|
||||||
|
icon: <PowerOffIcon size={14} />,
|
||||||
|
},
|
||||||
|
[AppSecMode.ENFORCE]: {
|
||||||
|
label: "Enforce",
|
||||||
|
description:
|
||||||
|
"Flagged requests are blocked with 403. Every request is inspected before it reaches the service, and requests are denied if the engine is unreachable (fail-closed).",
|
||||||
|
icon: <ShieldCheckIcon size={14} />,
|
||||||
|
},
|
||||||
|
[AppSecMode.OBSERVE]: {
|
||||||
|
label: "Observe",
|
||||||
|
description:
|
||||||
|
"Flagged requests are logged but still forwarded. Use this to evaluate the rules against real traffic before enforcing.",
|
||||||
|
icon: <EyeIcon size={14} />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReverseProxyAppSecInspection = ({ value, onChange }: Props) => {
|
||||||
|
const selected = APPSEC_OPTIONS[value];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-0 justify-between mb-6">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"h-12 w-12 flex items-center justify-center rounded-md bg-nb-gray-900/70 p-2 border border-nb-gray-900/70 shrink-0 relative"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={CrowdSecIconImage}
|
||||||
|
alt={"CrowdSec"}
|
||||||
|
className={"rounded-[4px]"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>CrowdSec AppSec (WAF)</Label>
|
||||||
|
<HelpText>
|
||||||
|
Inspect HTTP requests for exploits.{" "}
|
||||||
|
<b className={"text-white"}>Enforce</b> to block them or{" "}
|
||||||
|
<b className={"text-white"}>Observe</b> to only log without
|
||||||
|
blocking.
|
||||||
|
</HelpText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select value={value} onValueChange={(v) => onChange(v as AppSecMode)}>
|
||||||
|
<SelectTrigger className="w-[260px]" data-testid="appsec-mode-trigger">
|
||||||
|
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||||
|
{selected.icon}
|
||||||
|
<SelectValue />
|
||||||
|
</div>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.entries(APPSEC_OPTIONS).map(([mode, config]) => (
|
||||||
|
<SelectItem
|
||||||
|
key={mode}
|
||||||
|
value={mode}
|
||||||
|
data-testid={`appsec-mode-${mode}`}
|
||||||
|
extra={
|
||||||
|
config.description ? (
|
||||||
|
<HelpTooltip
|
||||||
|
triggerClassName="ml-[0.01rem]"
|
||||||
|
align="center"
|
||||||
|
side="right"
|
||||||
|
content={<>{config.description}</>}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="whitespace-nowrap">{config.label}</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -814,6 +814,7 @@ export default function ReverseProxyModal({
|
|||||||
onChange={setAccessRestrictions}
|
onChange={setAccessRestrictions}
|
||||||
onValidationChange={setAccessControlHasErrors}
|
onValidationChange={setAccessControlHasErrors}
|
||||||
supportsCrowdSec={selectedDomain?.supports_crowdsec}
|
supportsCrowdSec={selectedDomain?.supports_crowdsec}
|
||||||
|
supportsAppSec={!isL4Mode && selectedDomain?.supports_appsec}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ export default function ClustersFeaturesCell({ cluster }: Readonly<Props>) {
|
|||||||
icon: <ShieldAlert size={14} className={"text-green-500"} />,
|
icon: <ShieldAlert size={14} className={"text-green-500"} />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (cluster.supports_appsec) {
|
||||||
|
features.push({
|
||||||
|
key: "appsec",
|
||||||
|
label: "AppSec (WAF)",
|
||||||
|
description:
|
||||||
|
"Cluster has a CrowdSec AppSec endpoint configured across all active proxies, so services can inspect HTTP requests for exploits.",
|
||||||
|
icon: <ShieldAlert size={14} className={"text-green-500"} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (cluster.private) {
|
if (cluster.private) {
|
||||||
features.push({
|
features.push({
|
||||||
key: "private",
|
key: "private",
|
||||||
@@ -56,8 +65,7 @@ export default function ClustersFeaturesCell({ cluster }: Readonly<Props>) {
|
|||||||
Lets you publish services that are only reachable from peers in your
|
Lets you publish services that are only reachable from peers in your
|
||||||
NetBird network. Required for{" "}
|
NetBird network. Required for{" "}
|
||||||
<span className={"font-medium text-white"}>NetBird-Only Access</span>{" "}
|
<span className={"font-medium text-white"}>NetBird-Only Access</span>{" "}
|
||||||
and{" "}
|
and <span className={"font-medium text-white"}>Proxy Cluster</span>{" "}
|
||||||
<span className={"font-medium text-white"}>Proxy Cluster</span>{" "}
|
|
||||||
target types.
|
target types.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -111,6 +111,21 @@ export const ReverseProxyEventsAuthMethodCell = ({
|
|||||||
icon: <ShieldOff size={12} />,
|
icon: <ShieldOff size={12} />,
|
||||||
label: "CrowdSec Unavailable",
|
label: "CrowdSec Unavailable",
|
||||||
};
|
};
|
||||||
|
case "appsec_ban":
|
||||||
|
return {
|
||||||
|
icon: <ShieldAlert size={12} />,
|
||||||
|
label: "AppSec Ban",
|
||||||
|
};
|
||||||
|
case "appsec_captcha":
|
||||||
|
return {
|
||||||
|
icon: <ShieldAlert size={12} />,
|
||||||
|
label: "AppSec Captcha",
|
||||||
|
};
|
||||||
|
case "appsec_unavailable":
|
||||||
|
return {
|
||||||
|
icon: <ShieldOff size={12} />,
|
||||||
|
label: "AppSec Unavailable",
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
icon: null,
|
icon: null,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ const VERDICT_LABELS: Record<string, string> = {
|
|||||||
crowdsec_ban: "Ban",
|
crowdsec_ban: "Ban",
|
||||||
crowdsec_captcha: "Captcha",
|
crowdsec_captcha: "Captcha",
|
||||||
crowdsec_throttle: "Throttle",
|
crowdsec_throttle: "Throttle",
|
||||||
|
appsec_ban: "Ban",
|
||||||
|
appsec_captcha: "Captcha",
|
||||||
|
appsec_unavailable: "Unavailable",
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -17,12 +20,16 @@ type Props = {
|
|||||||
|
|
||||||
export const ReverseProxyEventsReasonCell = ({ event }: Props) => {
|
export const ReverseProxyEventsReasonCell = ({ event }: Props) => {
|
||||||
const metadata = event.metadata;
|
const metadata = event.metadata;
|
||||||
const verdict = metadata?.crowdsec_verdict;
|
// Only one of the two can be the reason a request was flagged without being
|
||||||
|
// blocked, since an enforced block sets auth_method_used instead.
|
||||||
|
const source = metadata?.appsec_verdict ? "appsec" : "crowdsec";
|
||||||
|
const verdictKey = `${source}_verdict`;
|
||||||
|
const verdict = metadata?.[verdictKey];
|
||||||
|
|
||||||
if (verdict && !event.auth_method_used?.startsWith("crowdsec_")) {
|
if (verdict && !event.auth_method_used?.startsWith(`${source}_`)) {
|
||||||
const verdictLabel = VERDICT_LABELS[verdict] ?? verdict;
|
const verdictLabel = VERDICT_LABELS[verdict] ?? verdict;
|
||||||
const metaEntries = Object.entries(metadata!).filter(
|
const metaEntries = Object.entries(metadata!).filter(
|
||||||
([k]) => k !== "crowdsec_verdict",
|
([k]) => k !== verdictKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,7 +56,8 @@ export const ReverseProxyEventsReasonCell = ({ event }: Props) => {
|
|||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<Badge variant="gray" className="gap-1.5">
|
<Badge variant="gray" className="gap-1.5">
|
||||||
<ShieldAlert size={12} className="text-yellow-500" />
|
<ShieldAlert size={12} className="text-yellow-500" />
|
||||||
CrowdSec Observe: {verdictLabel}
|
{source === "appsec" ? "AppSec" : "CrowdSec"} Observe:{" "}
|
||||||
|
{verdictLabel}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</FullTooltip>
|
</FullTooltip>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useMemo } from "react";
|
|||||||
import { useCountries } from "@/contexts/CountryProvider";
|
import { useCountries } from "@/contexts/CountryProvider";
|
||||||
import { usePermissions } from "@/contexts/PermissionsProvider";
|
import { usePermissions } from "@/contexts/PermissionsProvider";
|
||||||
import { useReverseProxies } from "@/contexts/ReverseProxiesProvider";
|
import { useReverseProxies } from "@/contexts/ReverseProxiesProvider";
|
||||||
import { CrowdSecMode, ReverseProxy } from "@/interfaces/ReverseProxy";
|
import { AppSecMode, CrowdSecMode, ReverseProxy } from "@/interfaces/ReverseProxy";
|
||||||
|
|
||||||
type RuleEntry = {
|
type RuleEntry = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -44,21 +44,35 @@ export default function ReverseProxyAccessControlCell({
|
|||||||
const canConfigure = !!permission?.services?.update;
|
const canConfigure = !!permission?.services?.update;
|
||||||
const restrictions = reverseProxy.access_restrictions;
|
const restrictions = reverseProxy.access_restrictions;
|
||||||
|
|
||||||
const supportsCrowdSec = domains?.find(
|
// Services on a custom domain carry its target_cluster in proxy_cluster, so
|
||||||
(d) => d.domain === reverseProxy.proxy_cluster,
|
// matching on domain alone misses them and reports no capability.
|
||||||
)?.supports_crowdsec;
|
const clusterDomain = domains?.find(
|
||||||
|
(d) =>
|
||||||
|
d.domain === reverseProxy.proxy_cluster ||
|
||||||
|
d.target_cluster === reverseProxy.proxy_cluster,
|
||||||
|
);
|
||||||
|
|
||||||
|
const supportsCrowdSec = clusterDomain?.supports_crowdsec;
|
||||||
|
|
||||||
const hasCrowdSec =
|
const hasCrowdSec =
|
||||||
supportsCrowdSec &&
|
supportsCrowdSec &&
|
||||||
restrictions?.crowdsec_mode != null &&
|
restrictions?.crowdsec_mode != null &&
|
||||||
restrictions.crowdsec_mode !== CrowdSecMode.OFF;
|
restrictions.crowdsec_mode !== CrowdSecMode.OFF;
|
||||||
|
|
||||||
|
const supportsAppSec = clusterDomain?.supports_appsec;
|
||||||
|
|
||||||
|
const hasAppSec =
|
||||||
|
supportsAppSec &&
|
||||||
|
restrictions?.appsec_mode != null &&
|
||||||
|
restrictions.appsec_mode !== AppSecMode.OFF;
|
||||||
|
|
||||||
const ruleCount =
|
const ruleCount =
|
||||||
(restrictions?.allowed_cidrs?.length ?? 0) +
|
(restrictions?.allowed_cidrs?.length ?? 0) +
|
||||||
(restrictions?.blocked_cidrs?.length ?? 0) +
|
(restrictions?.blocked_cidrs?.length ?? 0) +
|
||||||
(restrictions?.allowed_countries?.length ?? 0) +
|
(restrictions?.allowed_countries?.length ?? 0) +
|
||||||
(restrictions?.blocked_countries?.length ?? 0) +
|
(restrictions?.blocked_countries?.length ?? 0) +
|
||||||
(hasCrowdSec ? 1 : 0);
|
(hasCrowdSec ? 1 : 0) +
|
||||||
|
(hasAppSec ? 1 : 0);
|
||||||
|
|
||||||
const rulesBadge = (
|
const rulesBadge = (
|
||||||
<Badge
|
<Badge
|
||||||
@@ -163,8 +177,20 @@ export default function ReverseProxyAccessControlCell({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasAppSec) {
|
||||||
|
entries.push({
|
||||||
|
key: "appsec",
|
||||||
|
label: "AppSec (WAF)",
|
||||||
|
Icon: ShieldAlert,
|
||||||
|
value:
|
||||||
|
restrictions?.appsec_mode === AppSecMode.ENFORCE
|
||||||
|
? "Enforce"
|
||||||
|
: "Observe",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}, [restrictions, countries, hasCrowdSec]);
|
}, [restrictions, countries, hasCrowdSec, hasAppSec]);
|
||||||
|
|
||||||
const showRulesHover = ruleGroups.length > 0;
|
const showRulesHover = ruleGroups.length > 0;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user