Compare commits

...
Author SHA1 Message Date
Viktor Liu e8c6d44452 Add transparent proxy inspection policies 2026-04-18 13:13:18 +02:00
38 changed files with 3228 additions and 143 deletions

No files matched your search

+10 -19
View File
@@ -58,6 +58,7 @@
"ip-address": "^10.1.0", "ip-address": "^10.1.0",
"ip-cidr": "^3.1.0", "ip-cidr": "^3.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"js-yaml": "^4.1.1",
"lodash": "^4.17.23", "lodash": "^4.17.23",
"lucide-react": "^0.566.0", "lucide-react": "^0.566.0",
"next": "16.1.7", "next": "16.1.7",
@@ -85,6 +86,7 @@
"@faker-js/faker": "^9.5.1", "@faker-js/faker": "^9.5.1",
"@types/chroma-js": "^3.1.1", "@types/chroma-js": "^3.1.1",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-next": "^16.1.6", "eslint-config-next": "^16.1.6",
"postcss": "^8", "postcss": "^8",
@@ -168,7 +170,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -2998,6 +2999,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3031,7 +3039,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -3041,7 +3048,6 @@
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@@ -3100,7 +3106,6 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0", "@typescript-eslint/types": "8.54.0",
@@ -3581,8 +3586,7 @@
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/@xyflow/react": { "node_modules/@xyflow/react": {
"version": "12.10.0", "version": "12.10.0",
@@ -3621,7 +3625,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -4044,7 +4047,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -4667,7 +4669,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC", "license": "ISC",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
@@ -5196,7 +5197,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -5394,7 +5394,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@rtsao/scc": "^1.1.0", "@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9", "array-includes": "^3.1.9",
@@ -6688,7 +6687,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@@ -7446,7 +7444,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -7654,7 +7651,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -7695,7 +7691,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@@ -8590,7 +8585,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -8757,7 +8751,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -8923,7 +8916,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -9251,7 +9243,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
+2
View File
@@ -66,6 +66,7 @@
"ip-address": "^10.1.0", "ip-address": "^10.1.0",
"ip-cidr": "^3.1.0", "ip-cidr": "^3.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"js-yaml": "^4.1.1",
"lodash": "^4.17.23", "lodash": "^4.17.23",
"lucide-react": "^0.566.0", "lucide-react": "^0.566.0",
"next": "16.1.7", "next": "16.1.7",
@@ -96,6 +97,7 @@
"@faker-js/faker": "^9.5.1", "@faker-js/faker": "^9.5.1",
"@types/chroma-js": "^3.1.1", "@types/chroma-js": "^3.1.1",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-next": "^16.1.6", "eslint-config-next": "^16.1.6",
"postcss": "^8", "postcss": "^8",
@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Inspection Policies - ${globalMetaTitle}`,
};
export default BlankLayout;
@@ -0,0 +1,61 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon, ShieldCheck } from "lucide-react";
import React, { lazy, Suspense } from "react";
import AccessControlIcon from "@/assets/icons/AccessControlIcon";
import { usePermissions } from "@/contexts/PermissionsProvider";
import PoliciesProvider from "@/contexts/PoliciesProvider";
import { InspectionPolicy } from "@/interfaces/Network";
import PageContainer from "@/layouts/PageContainer";
const InspectionPoliciesTable = lazy(
() =>
import(
"@/modules/networks/inspection/InspectionPoliciesTable"
),
);
export default function InspectionPoliciesPage() {
const { permission } = usePermissions();
const { data: policies, isLoading } =
useFetchApi<InspectionPolicy[]>("/inspection-policies");
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/access-control"}
label={"Access Control"}
icon={<AccessControlIcon size={14} />}
/>
<Breadcrumbs.Item
href={"/access-control/inspection-policies"}
label={"Inspection Policies"}
icon={<ShieldCheck size={15} />}
/>
</Breadcrumbs>
<h1>Inspection Policies</h1>
<Paragraph>
Create reusable inspection rule sets for transparent proxy traffic
inspection. Attach them to access control policies to inspect traffic
flowing through routing peers.
</Paragraph>
</div>
<PoliciesProvider>
<Suspense fallback={<SkeletonTable />}>
<InspectionPoliciesTable
policies={policies ?? []}
isLoading={isLoading}
/>
</Suspense>
</PoliciesProvider>
</PageContainer>
);
}
+92 -108
View File
@@ -2,7 +2,6 @@
import Breadcrumbs from "@components/Breadcrumbs"; import Breadcrumbs from "@components/Breadcrumbs";
import Button from "@components/Button"; import Button from "@components/Button";
import { Callout } from "@components/Callout";
import Card from "@components/Card"; import Card from "@components/Card";
import HelpText from "@components/HelpText"; import HelpText from "@components/HelpText";
import { Input } from "@components/Input"; import { Input } from "@components/Input";
@@ -72,6 +71,7 @@ import ReverseProxiesProvider, {
useReverseProxies, useReverseProxies,
} from "@/contexts/ReverseProxiesProvider"; } from "@/contexts/ReverseProxiesProvider";
import { ReverseProxyFlatTargetsTabContent } from "@/modules/reverse-proxy/targets/flat/ReverseProxyFlatTargetsTabContent"; import { ReverseProxyFlatTargetsTabContent } from "@/modules/reverse-proxy/targets/flat/ReverseProxyFlatTargetsTabContent";
import { PeerEditIPModal } from "@/modules/peer/PeerEditIPModal";
import { PeerSSHToggle } from "@/modules/peer/PeerSSHToggle"; import { PeerSSHToggle } from "@/modules/peer/PeerSSHToggle";
import { RDPButton } from "@/modules/remote-access/rdp/RDPButton"; import { RDPButton } from "@/modules/remote-access/rdp/RDPButton";
import { SSHButton } from "@/modules/remote-access/ssh/SSHButton"; import { SSHButton } from "@/modules/remote-access/ssh/SSHButton";
@@ -469,31 +469,55 @@ function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
const { update } = usePeer(); const { update } = usePeer();
const { mutate } = useSWRConfig(); const { mutate } = useSWRConfig();
const [showEditIPModal, setShowEditIPModal] = useState(false); const [showEditIPModal, setShowEditIPModal] = useState(false);
const [showEditIPv6Modal, setShowEditIPv6Modal] = useState(false);
const { permission } = usePermissions(); const { permission } = usePermissions();
const countryText = useMemo(() => { const countryText = useMemo(() => {
return getRegionByPeer(peer); return getRegionByPeer(peer);
}, [getRegionByPeer, peer]); }, [getRegionByPeer, peer]);
const handleSaveIP = (newIP: string) => {
notify({
title: peer.name,
description: "NetBird Peer IP was successfully updated",
promise: update({ ip: newIP }).then(() => {
mutate("/peers/" + peer.id);
setShowEditIPModal(false);
}),
loadingMessage: "Updating peer IP...",
});
};
const handleSaveIPv6 = (newIPv6: string) => {
notify({
title: peer.name,
description: "NetBird Peer IPv6 was successfully updated",
promise: update({ ipv6: newIPv6 }).then(() => {
mutate("/peers/" + peer.id);
setShowEditIPv6Modal(false);
}),
loadingMessage: "Updating peer IPv6...",
});
};
return ( return (
<> <>
<Modal open={showEditIPModal} onOpenChange={setShowEditIPModal}> <PeerEditIPModal
<EditIPModal version="v4"
onSuccess={(newIP) => { currentIP={peer.ip}
notify({ open={showEditIPModal}
title: peer.name, onOpenChange={setShowEditIPModal}
description: "Peer IP was successfully updated", onSave={handleSaveIP}
promise: update({ ip: newIP }).then(() => { key={showEditIPModal ? "v4-open" : "v4-closed"}
mutate("/peers/" + peer.id); />
setShowEditIPModal(false); <PeerEditIPModal
}), version="v6"
loadingMessage: "Updating peer IP...", currentIP={peer.ipv6 || ""}
}); open={showEditIPv6Modal}
}} onOpenChange={setShowEditIPv6Modal}
peer={peer} onSave={handleSaveIPv6}
key={showEditIPModal ? 1 : 0} key={showEditIPv6Modal ? "v6-open" : "v6-closed"}
/> />
</Modal>
<Card className={"w-full xl:w-1/2"}> <Card className={"w-full xl:w-1/2"}>
<Card.List> <Card.List>
<Card.ListItem <Card.ListItem
@@ -508,23 +532,36 @@ function PeerInformationCard({ peer }: Readonly<{ peer: Peer }>) {
} }
valueToCopy={peer.ip} valueToCopy={peer.ip}
value={ value={
<div className="flex items-center gap-2 justify-between w-full"> <EditableValue
<span>{peer.ip}</span> value={peer.ip}
{permission.peers.update && ( canEdit={permission.peers.update}
<button onEdit={() => setShowEditIPModal(true)}
className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer" />
onClick={(e) => {
e.stopPropagation();
setShowEditIPModal(true);
}}
>
<PencilIcon size={14} />
</button>
)}
</div>
} }
/> />
{peer.ipv6 && (
<Card.ListItem
copy
tooltip={false}
copyText={"NetBird IPv6 Address"}
label={
<>
<MapPin size={16} />
NetBird IPv6 Address
</>
}
valueToCopy={peer.ipv6}
value={
<EditableValue
value={peer.ipv6}
canEdit={permission.peers.update}
onEdit={() => setShowEditIPv6Modal(true)}
/>
}
/>
)}
<Card.ListItem <Card.ListItem
copy copy
copyText={"Public IP Address"} copyText={"Public IP Address"}
@@ -765,82 +802,29 @@ function EditNameModal({ onSuccess, peer, initialName }: Readonly<ModalProps>) {
); );
} }
interface EditIPModalProps { function EditableValue({
onSuccess: (ip: string) => void; value,
peer: Peer; canEdit,
} onEdit,
}: {
function EditIPModal({ onSuccess, peer }: Readonly<EditIPModalProps>) { value: string;
const [ip, setIP] = useState(peer.ip); canEdit: boolean;
const [error, setError] = useState(""); onEdit: () => void;
}) {
const validateIP = (ipAddress: string) => {
const ipRegex =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return ipRegex.test(ipAddress);
};
const isDisabled = useMemo(() => {
if (ip === peer.ip) return true;
const trimmedIP = trim(ip);
return trimmedIP.length === 0 || !validateIP(ip);
}, [ip, peer.ip]);
React.useEffect(() => {
switch (true) {
case ip === peer.ip:
setError("");
break;
case !validateIP(ip):
setError("Please enter a valid IP, e.g., 100.64.0.15");
break;
default:
setError("");
break;
}
}, [ip, peer.ip]);
return ( return (
<ModalContent maxWidthClass={"max-w-md"}> <div className="flex items-center gap-2 justify-between w-full">
<form> <span>{value}</span>
<ModalHeader {canEdit && (
title={"Edit Peer IP Address"} <button
description={"Update the NetBird IP address for this peer."} className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
color={"blue"} onClick={(e) => {
/> e.stopPropagation();
onEdit();
<div className={"p-default flex flex-col gap-4"}> }}
<div> >
<Input <PencilIcon size={14} />
placeholder={"e.g., 100.64.0.15"} </button>
value={ip} )}
onChange={(e) => setIP(e.target.value)} </div>
error={error}
/>
</div>
<Callout>Changes take effect when the peer reconnects.</Callout>
</div>
<ModalFooter className={"items-center"} separator={false}>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"} className={"w-full"}>
Cancel
</Button>
</ModalClose>
<Button
variant={"primary"}
className={"w-full"}
onClick={() => onSuccess(ip)}
disabled={isDisabled}
>
Save
</Button>
</div>
</ModalFooter>
</form>
</ModalContent>
); );
} }
+13 -6
View File
@@ -18,7 +18,7 @@ import {
} from "@utils/version"; } from "@utils/version";
export default function SSHPage() { export default function SSHPage() {
const { peerId, username, port } = useSSHQueryParams(); const { peerId, username, port, ipVersion } = useSSHQueryParams();
const { const {
data: peer, data: peer,
@@ -48,6 +48,7 @@ export default function SSHPage() {
peer={peer} peer={peer}
username={username} username={username}
port={port} port={port}
ipVersion={ipVersion}
/> />
) : ( ) : (
<LoadingMessage message={"Starting ssh session..."} /> <LoadingMessage message={"Starting ssh session..."} />
@@ -60,9 +61,10 @@ type Props = {
username: string; username: string;
port: string; port: string;
peer: Peer; peer: Peer;
ipVersion: string | null;
}; };
function SSHTerminal({ username, port, peer }: Props) { function SSHTerminal({ username, port, peer, ipVersion }: Props) {
const client = useNetBirdClient(); const client = useNetBirdClient();
const connected = useRef(false); const connected = useRef(false);
const sshConnectedOnce = useRef(false); const sshConnectedOnce = useRef(false);
@@ -81,9 +83,12 @@ function SSHTerminal({ username, port, peer }: Props) {
const isClientDisconnected = client.status === NetBirdStatus.DISCONNECTED; const isClientDisconnected = client.status === NetBirdStatus.DISCONNECTED;
const isClientConnecting = client.status === NetBirdStatus.CONNECTING; const isClientConnecting = client.status === NetBirdStatus.CONNECTING;
// Use the FQDN when an IP version is specified so the dialer resolves to the correct address family.
const sshHost = ipVersion ? peer.dns_label || peer.ip : peer.ip;
useEffect(() => { useEffect(() => {
document.title = `${username}@${peer.ip} - ${peer.hostname}`; document.title = `${username}@${sshHost} - ${peer.hostname}`;
}, [username, peer, client]); }, [username, peer, client, sshHost]);
const handleReconnect = async () => { const handleReconnect = async () => {
if (!peer?.id) return; if (!peer?.id) return;
@@ -97,9 +102,10 @@ function SSHTerminal({ username, port, peer }: Props) {
const rules = [`${protocol}/${aclPort}`]; const rules = [`${protocol}/${aclPort}`];
await client?.connectTemporary(peer.id, rules); await client?.connectTemporary(peer.id, rules);
await ssh({ await ssh({
hostname: peer.ip, hostname: sshHost,
port: Number(port), port: Number(port),
username, username,
ipVersion: ipVersion || undefined,
}); });
} catch (error) { } catch (error) {
console.error("Reconnection failed:", error); console.error("Reconnection failed:", error);
@@ -123,9 +129,10 @@ function SSHTerminal({ username, port, peer }: Props) {
const rules = [`${protocol}/${aclPort}`]; const rules = [`${protocol}/${aclPort}`];
await client?.connectTemporary(peer.id, rules); await client?.connectTemporary(peer.id, rules);
const res = await ssh({ const res = await ssh({
hostname: peer.ip, hostname: sshHost,
port: Number(port), port: Number(port),
username, username,
ipVersion: ipVersion || undefined,
}); });
if (res === SSHStatus.CONNECTED) { if (res === SSHStatus.CONNECTED) {
sshConnectedOnce.current = true; sshConnectedOnce.current = true;
+7 -2
View File
@@ -28,8 +28,13 @@ export const DeviceCard = ({
const descriptionText = useMemo(() => { const descriptionText = useMemo(() => {
return description !== undefined return description !== undefined
? description ? description
: address || device?.ip || resource?.address; : address ||
}, [description, address, device]); (device?.ip
? device.ipv6
? `${device.ip}, ${device.ipv6}`
: device.ip
: resource?.address);
}, [description, address, device, resource]);
return ( return (
<div <div
+3 -1
View File
@@ -968,7 +968,8 @@ const ResourcesList = ({
const peersSearchPredicate = (item: Peer, query: string) => { const peersSearchPredicate = (item: Peer, query: string) => {
const lowerCaseQuery = query.toLowerCase(); const lowerCaseQuery = query.toLowerCase();
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true; if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
return item.ip.toLowerCase().includes(lowerCaseQuery); if (item.ip.toLowerCase().includes(lowerCaseQuery)) return true;
return item.ipv6?.toLowerCase().includes(lowerCaseQuery) ?? false;
}; };
const PeersList = ({ const PeersList = ({
@@ -1059,6 +1060,7 @@ const PeersList = ({
} }
> >
{res.ip} {res.ip}
{res.ipv6 && `, ${res.ipv6}`}
<RadioItem value={res.id} /> <RadioItem value={res.id} />
</div> </div>
</div> </div>
+4 -1
View File
@@ -30,7 +30,8 @@ const searchPredicate = (item: Peer, query: string) => {
const lowerCaseQuery = query.toLowerCase(); const lowerCaseQuery = query.toLowerCase();
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true; if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
if (item.hostname.toLowerCase().includes(lowerCaseQuery)) return true; if (item.hostname.toLowerCase().includes(lowerCaseQuery)) return true;
return item.ip.toLowerCase().startsWith(lowerCaseQuery); if (item.ip.toLowerCase().startsWith(lowerCaseQuery)) return true;
return !!item.ipv6?.toLowerCase().startsWith(lowerCaseQuery);
}; };
export function PeerSelector({ export function PeerSelector({
@@ -126,6 +127,7 @@ export function PeerSelector({
> >
<MapPinIcon /> <MapPinIcon />
{value.ip} {value.ip}
{value.ipv6 && `, ${value.ipv6}`}
</div> </div>
</div> </div>
) : ( ) : (
@@ -240,6 +242,7 @@ export function PeerSelector({
> >
<MapPinIcon /> <MapPinIcon />
{option.ip} {option.ip}
{option.ipv6 && `, ${option.ipv6}`}
</div> </div>
</FullTooltip> </FullTooltip>
); );
+3
View File
@@ -30,6 +30,7 @@ const PeerContext = React.createContext(
inactivityExpiration?: boolean; inactivityExpiration?: boolean;
approval_required?: boolean; approval_required?: boolean;
ip?: string; ip?: string;
ipv6?: string;
}) => Promise<Peer>; }) => Promise<Peer>;
toggleSSH: (newState: boolean) => Promise<void>; toggleSSH: (newState: boolean) => Promise<void>;
setSSHInstructionsModal: (open: boolean) => void; setSSHInstructionsModal: (open: boolean) => void;
@@ -80,6 +81,7 @@ export default function PeerProvider({
inactivityExpiration?: boolean; inactivityExpiration?: boolean;
approval_required?: boolean; approval_required?: boolean;
ip?: string; ip?: string;
ipv6?: string;
}) => { }) => {
return peerRequest.put( return peerRequest.put(
{ {
@@ -99,6 +101,7 @@ export default function PeerProvider({
? undefined ? undefined
: props.approval_required, : props.approval_required,
ip: props.ip != undefined ? props.ip : undefined, ip: props.ip != undefined ? props.ip : undefined,
ipv6: props.ipv6 != undefined ? props.ipv6 : undefined,
}, },
`/${peer.id}`, `/${peer.id}`,
); );
+7
View File
@@ -83,6 +83,9 @@ export default function PoliciesProvider({ children }: Props) {
source_posture_checks: (policy.source_posture_checks ?? []).map((c) => source_posture_checks: (policy.source_posture_checks ?? []).map((c) =>
typeof c === "string" ? c : c.id, typeof c === "string" ? c : c.id,
), ),
inspection_policies: (policy.inspection_policies ?? []).map((p) =>
typeof p === "string" ? p : p.id,
),
rules: [ rules: [
{ {
...rule, ...rule,
@@ -154,6 +157,10 @@ export default function PoliciesProvider({ children }: Props) {
toUpdate.source_posture_checks ?? toUpdate.source_posture_checks ??
policy.source_posture_checks ?? policy.source_posture_checks ??
[], [],
inspection_policies:
toUpdate.inspection_policies ??
policy.inspection_policies ??
[],
}, },
`/${policy.id}`, `/${policy.id}`,
) )
+2
View File
@@ -28,6 +28,8 @@ export interface Account {
auto_update_version: string; auto_update_version: string;
auto_update_always: boolean; auto_update_always: boolean;
local_auth_disabled?: boolean; local_auth_disabled?: boolean;
ipv6_enabled_groups?: string[];
network_range_v6?: string;
}; };
onboarding?: AccountOnboarding; onboarding?: AccountOnboarding;
} }
+81
View File
@@ -17,9 +17,90 @@ export interface NetworkRouter {
metric: number; metric: number;
masquerade: boolean; masquerade: boolean;
enabled: boolean; enabled: boolean;
inspection?: InspectionConfig;
search?: string; search?: string;
} }
export type InspectionAction = "allow" | "block" | "inspect";
export type InspectionMode = "builtin" | "envoy" | "external";
export interface InspectionEnvoySnippets {
http_filters?: string;
network_filters?: string;
clusters?: string;
}
export interface InspectionConfig {
enabled: boolean;
mode?: InspectionMode;
external_url?: string;
default_action?: InspectionAction;
redirect_ports?: number[];
rules?: InspectionRule[];
icap?: InspectionICAPConfig;
ca_cert_pem?: string;
ca_key_pem?: string;
listen_port?: number;
}
export interface InspectionRule {
id?: string;
name: string;
enabled: boolean;
domains?: string[];
networks?: string[];
ports?: number[];
action: InspectionAction;
priority: number;
}
// Reusable inspection policy referenced from access control policies.
// Contains both L7 rules and proxy infrastructure config (CA, ICAP, mode).
export interface InspectionPolicy {
id?: string;
name: string;
description?: string;
enabled: boolean;
rules: InspectionPolicyRule[];
// Proxy infrastructure config
mode?: InspectionMode;
external_url?: string;
default_action?: InspectionAction;
redirect_ports?: number[];
ca_cert_pem?: string;
ca_key_pem?: string;
icap?: InspectionICAPConfig;
// Envoy sidecar config (mode "envoy" only)
envoy_binary_path?: string;
envoy_admin_port?: number;
envoy_snippets?: InspectionEnvoySnippets;
}
export interface InspectionPolicyRule {
domains?: string[];
networks?: string[];
protocols?: InspectionProtocol[];
paths?: string[];
action: InspectionAction;
priority: number;
}
export type InspectionProtocol =
| "http"
| "https"
| "h2"
| "h3"
| "websocket"
| "other";
export interface InspectionICAPConfig {
reqmod_url?: string;
respmod_url?: string;
max_connections?: number;
}
export interface NetworkResource { export interface NetworkResource {
id: string; id: string;
name: string; name: string;
+1
View File
@@ -5,6 +5,7 @@ export interface Peer {
id?: string; id?: string;
name: string; name: string;
ip: string; ip: string;
ipv6?: string;
connected: boolean; connected: boolean;
created_at?: Date; created_at?: Date;
last_seen: Date; last_seen: Date;
+2
View File
@@ -1,4 +1,5 @@
import { Group } from "@/interfaces/Group"; import { Group } from "@/interfaces/Group";
import { InspectionPolicy } from "@/interfaces/Network";
import { PostureCheck } from "@/interfaces/PostureCheck"; import { PostureCheck } from "@/interfaces/PostureCheck";
export interface Policy { export interface Policy {
@@ -9,6 +10,7 @@ export interface Policy {
query?: string; query?: string;
rules: PolicyRule[]; rules: PolicyRule[];
source_posture_checks: string[] | PostureCheck[]; source_posture_checks: string[] | PostureCheck[];
inspection_policies?: string[] | InspectionPolicy[];
} }
export interface PolicyRule { export interface PolicyRule {
+7
View File
@@ -121,6 +121,13 @@ export default function Navigation({
href={"/groups"} href={"/groups"}
visible={permission.policies.read} visible={permission.policies.read}
/> />
<SidebarItem
label="Inspection Policies"
isChild
href={"/access-control/inspection-policies"}
exactPathMatch={true}
visible={permission.policies.read}
/>
<SidebarItem <SidebarItem
label="Posture Checks" label="Posture Checks"
isChild isChild
@@ -50,6 +50,9 @@ import { NetworkResource } from "@/interfaces/Network";
import { Policy, PolicyRuleResource, Protocol } from "@/interfaces/Policy"; import { Policy, PolicyRuleResource, Protocol } from "@/interfaces/Policy";
import { PostureCheck } from "@/interfaces/PostureCheck"; import { PostureCheck } from "@/interfaces/PostureCheck";
import { useAccessControl } from "@/modules/access-control/useAccessControl"; import { useAccessControl } from "@/modules/access-control/useAccessControl";
import { InspectionPolicy } from "@/interfaces/Network";
import { InspectionTab } from "@/modules/networks/inspection/InspectionTab";
import { InspectionTabTrigger } from "@/modules/networks/inspection/InspectionTabTrigger";
import { PostureCheckTab } from "@/modules/posture-checks/ui/PostureCheckTab"; import { PostureCheckTab } from "@/modules/posture-checks/ui/PostureCheckTab";
import { PostureCheckTabTrigger } from "@/modules/posture-checks/ui/PostureCheckTabTrigger"; import { PostureCheckTabTrigger } from "@/modules/posture-checks/ui/PostureCheckTabTrigger";
import { SSHAccessType } from "@/modules/access-control/ssh/SSHAccessType"; import { SSHAccessType } from "@/modules/access-control/ssh/SSHAccessType";
@@ -186,6 +189,9 @@ export function AccessControlModalContent({
setSshAccessType, setSshAccessType,
sshAuthorizedGroups, sshAuthorizedGroups,
setSshAuthorizedGroups, setSshAuthorizedGroups,
inspectionPolicies,
setInspectionPolicies,
isInspectionPoliciesLoading,
} = useAccessControl({ } = useAccessControl({
policy, policy,
postureCheckTemplates, postureCheckTemplates,
@@ -202,6 +208,7 @@ export function AccessControlModalContent({
if (initialTab && initialTab !== "") return initialTab; if (initialTab && initialTab !== "") return initialTab;
if (!cell) return "policy"; if (!cell) return "policy";
if (cell == "posture_checks") return "posture_checks"; if (cell == "posture_checks") return "posture_checks";
if (cell == "inspection_policies") return "inspection";
return "policy"; return "policy";
}); });
@@ -252,6 +259,7 @@ export function AccessControlModalContent({
Policy Policy
</TabsTrigger> </TabsTrigger>
<PostureCheckTabTrigger disabled={!canContinueToPostureChecks} /> <PostureCheckTabTrigger disabled={!canContinueToPostureChecks} />
<InspectionTabTrigger disabled={!canContinueToPostureChecks} />
<TabsTrigger value={"general"} disabled={!canContinueToPostureChecks}> <TabsTrigger value={"general"} disabled={!canContinueToPostureChecks}>
<Text <Text
size={16} size={16}
@@ -518,6 +526,11 @@ export function AccessControlModalContent({
postureChecks={postureChecks} postureChecks={postureChecks}
setPostureChecks={setPostureChecks} setPostureChecks={setPostureChecks}
/> />
<InspectionTab
isLoading={isInspectionPoliciesLoading}
inspectionPolicies={inspectionPolicies}
setInspectionPolicies={setInspectionPolicies}
/>
<TabsContent value={"general"} className={"px-8 pb-6"}> <TabsContent value={"general"} className={"px-8 pb-6"}>
<div className={"flex flex-col gap-6"}> <div className={"flex flex-col gap-6"}>
<div> <div>
@@ -598,6 +611,24 @@ export function AccessControlModalContent({
> >
Back Back
</Button> </Button>
<Button
variant={"primary"}
onClick={() => setTab("inspection")}
disabled={!canContinueToPostureChecks}
>
Continue
</Button>
</>
)}
{tab == "inspection" && (
<>
<Button
variant={"secondary"}
onClick={() => setTab("posture_checks")}
>
Back
</Button>
<Button <Button
variant={"primary"} variant={"primary"}
onClick={() => setTab("general")} onClick={() => setTab("general")}
@@ -612,7 +643,7 @@ export function AccessControlModalContent({
<> <>
<Button <Button
variant={"secondary"} variant={"secondary"}
onClick={() => setTab("posture_checks")} onClick={() => setTab("inspection")}
> >
Back Back
</Button> </Button>
@@ -0,0 +1,39 @@
import Badge from "@components/Badge";
import { IconCirclePlus } from "@tabler/icons-react";
import { Eye } from "lucide-react";
import React from "react";
import { usePermissions } from "@/contexts/PermissionsProvider";
import { Policy } from "@/interfaces/Policy";
type Props = {
policy: Policy;
};
export default function AccessControlInspectionCell({ policy }: Props) {
const { permission } = usePermissions();
const isDisabled = !permission.policies.create || !permission.policies.update;
const count = policy.inspection_policies?.length ?? 0;
return count > 0 ? (
<div className="flex">
<Badge variant="gray" useHover={true}>
<Eye size={14} className="text-netbird" />
{count} Inspection
</Badge>
</div>
) : (
<div className="flex">
<Badge
variant="gray"
useHover={!isDisabled}
onClick={(e) => {
if (isDisabled) e.stopPropagation();
}}
disabled={isDisabled}
>
<IconCirclePlus size={14} />
Add Inspection
</Badge>
</div>
);
}
@@ -29,6 +29,7 @@ import AccessControlDestinationsCell from "@/modules/access-control/table/Access
import AccessControlDirectionCell from "@/modules/access-control/table/AccessControlDirectionCell"; import AccessControlDirectionCell from "@/modules/access-control/table/AccessControlDirectionCell";
import AccessControlNameCell from "@/modules/access-control/table/AccessControlNameCell"; import AccessControlNameCell from "@/modules/access-control/table/AccessControlNameCell";
import AccessControlPortsCell from "@/modules/access-control/table/AccessControlPortsCell"; import AccessControlPortsCell from "@/modules/access-control/table/AccessControlPortsCell";
import AccessControlInspectionCell from "@/modules/access-control/table/AccessControlInspectionCell";
import AccessControlPostureCheckCell from "@/modules/access-control/table/AccessControlPostureCheckCell"; import AccessControlPostureCheckCell from "@/modules/access-control/table/AccessControlPostureCheckCell";
import AccessControlProtocolCell from "@/modules/access-control/table/AccessControlProtocolCell"; import AccessControlProtocolCell from "@/modules/access-control/table/AccessControlProtocolCell";
import AccessControlSourcesCell from "@/modules/access-control/table/AccessControlSourcesCell"; import AccessControlSourcesCell from "@/modules/access-control/table/AccessControlSourcesCell";
@@ -165,6 +166,17 @@ export const AccessControlTableColumns: ColumnDef<Policy>[] = [
<AccessControlPostureCheckCell policy={cell.row.original} /> <AccessControlPostureCheckCell policy={cell.row.original} />
), ),
}, },
{
id: "inspection_policies",
accessorFn: (row) => row.inspection_policies?.length || 0,
sortingFn: "basic",
header: ({ column }) => {
return <DataTableHeader column={column}>Inspection</DataTableHeader>;
},
cell: ({ cell }) => (
<AccessControlInspectionCell policy={cell.row.original} />
),
},
{ {
id: "id", id: "id",
accessorKey: "id", accessorKey: "id",
@@ -14,6 +14,7 @@ import {
PortRange, PortRange,
Protocol, Protocol,
} from "@/interfaces/Policy"; } from "@/interfaces/Policy";
import { InspectionPolicy } from "@/interfaces/Network";
import { PostureCheck } from "@/interfaces/PostureCheck"; import { PostureCheck } from "@/interfaces/PostureCheck";
import useGroupHelper from "@/modules/groups/useGroupHelper"; import useGroupHelper from "@/modules/groups/useGroupHelper";
import { usePostureCheck } from "@/modules/posture-checks/usePostureCheck"; import { usePostureCheck } from "@/modules/posture-checks/usePostureCheck";
@@ -49,6 +50,11 @@ export const useAccessControl = ({
const [postureChecks, setPostureChecks] = useState<PostureCheck[]>([]); const [postureChecks, setPostureChecks] = useState<PostureCheck[]>([]);
const postureChecksLoaded = useRef(false); const postureChecksLoaded = useRef(false);
const { data: allInspectionPolicies, isLoading: isInspectionPoliciesLoading } =
useFetchApi<InspectionPolicy[]>("/inspection-policies");
const [inspectionPolicies, setInspectionPolicies] = useState<InspectionPolicy[]>([]);
const inspectionPoliciesLoaded = useRef(false);
const initialPostureChecks = useMemo(() => { const initialPostureChecks = useMemo(() => {
const foundChecks = const foundChecks =
allPostureChecks?.filter((check) => { allPostureChecks?.filter((check) => {
@@ -82,6 +88,23 @@ export const useAccessControl = ({
} }
}, [initialPostureChecks]); }, [initialPostureChecks]);
// Initialize inspection policies from existing policy
const initialInspectionPolicies = useMemo(() => {
if (!policy?.inspection_policies || !allInspectionPolicies) return [];
const ids = policy.inspection_policies.map((ip) =>
typeof ip === "string" ? ip : ip.id,
).filter(Boolean) as string[];
return allInspectionPolicies.filter((ip) => ip.id && ids.includes(ip.id));
}, [policy, allInspectionPolicies]);
useEffect(() => {
if (inspectionPoliciesLoaded.current) return;
if (initialInspectionPolicies.length > 0) {
inspectionPoliciesLoaded.current = true;
setInspectionPolicies(initialInspectionPolicies);
}
}, [initialInspectionPolicies]);
const { updatePolicy } = usePolicies(); const { updatePolicy } = usePolicies();
const firstRule = policy?.rules ? policy.rules[0] : undefined; const firstRule = policy?.rules ? policy.rules[0] : undefined;
@@ -191,6 +214,7 @@ export const useAccessControl = ({
description, description,
enabled, enabled,
source_posture_checks: postureChecks, source_posture_checks: postureChecks,
inspection_policies: inspectionPolicies,
rules: [ rules: [
{ {
bidirectional: direction == "bi", bidirectional: direction == "bi",
@@ -292,6 +316,9 @@ export const useAccessControl = ({
source_posture_checks: postureChecks source_posture_checks: postureChecks
? postureChecks.map((c) => c.id) ? postureChecks.map((c) => c.id)
: undefined, : undefined,
inspection_policies: inspectionPolicies
? inspectionPolicies.map((p) => p.id)
: undefined,
rules: [ rules: [
{ {
bidirectional: direction == "bi", bidirectional: direction == "bi",
@@ -407,10 +434,13 @@ export const useAccessControl = ({
setDestinationGroups, setDestinationGroups,
postureChecks, postureChecks,
setPostureChecks, setPostureChecks,
inspectionPolicies,
setInspectionPolicies,
submit, submit,
getPolicyData, getPolicyData,
portDisabled, portDisabled,
isPostureChecksLoading, isPostureChecksLoading,
isInspectionPoliciesLoading,
sourceResource, sourceResource,
setSourceResource, setSourceResource,
destinationResource, destinationResource,
Loaded 20 of 38 files, more files were not shown because too many files have changed in this diff. Show more