feat: implement useNetworkPolicy flag for applications and update related components

This commit is contained in:
biersoeckli
2025-12-22 13:32:56 +00:00
parent a7814f6395
commit 12c01e4e0c
12 changed files with 95 additions and 18 deletions

View File

@@ -0,0 +1,36 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_App" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"appType" TEXT NOT NULL DEFAULT 'APP',
"projectId" TEXT NOT NULL,
"sourceType" TEXT NOT NULL DEFAULT 'GIT',
"containerImageSource" TEXT,
"containerRegistryUsername" TEXT,
"containerRegistryPassword" TEXT,
"gitUrl" TEXT,
"gitBranch" TEXT,
"gitUsername" TEXT,
"gitToken" TEXT,
"dockerfilePath" TEXT NOT NULL DEFAULT './Dockerfile',
"replicas" INTEGER NOT NULL DEFAULT 1,
"envVars" TEXT NOT NULL DEFAULT '',
"memoryReservation" INTEGER,
"memoryLimit" INTEGER,
"cpuReservation" INTEGER,
"cpuLimit" INTEGER,
"webhookId" TEXT,
"ingressNetworkPolicy" TEXT NOT NULL DEFAULT 'ALLOW_ALL',
"egressNetworkPolicy" TEXT NOT NULL DEFAULT 'ALLOW_ALL',
"useNetworkPolicy" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "App_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_App" ("appType", "containerImageSource", "containerRegistryPassword", "containerRegistryUsername", "cpuLimit", "cpuReservation", "createdAt", "dockerfilePath", "egressNetworkPolicy", "envVars", "gitBranch", "gitToken", "gitUrl", "gitUsername", "id", "ingressNetworkPolicy", "memoryLimit", "memoryReservation", "name", "projectId", "replicas", "sourceType", "updatedAt", "webhookId") SELECT "appType", "containerImageSource", "containerRegistryPassword", "containerRegistryUsername", "cpuLimit", "cpuReservation", "createdAt", "dockerfilePath", "egressNetworkPolicy", "envVars", "gitBranch", "gitToken", "gitUrl", "gitUsername", "id", "ingressNetworkPolicy", "memoryLimit", "memoryReservation", "name", "projectId", "replicas", "sourceType", "updatedAt", "webhookId" FROM "App";
DROP TABLE "App";
ALTER TABLE "new_App" RENAME TO "App";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -200,8 +200,9 @@ model App {
webhookId String?
ingressNetworkPolicy String @default("ALLOW_ALL") // ALLOW_ALL, NAMESPACE_ONLY, DENY_ALL, INTERNET_ONLY
egressNetworkPolicy String @default("ALLOW_ALL") // ALLOW_ALL, NAMESPACE_ONLY, DENY_ALL, INTERNET_ONLY
ingressNetworkPolicy String @default("ALLOW_ALL") // ALLOW_ALL, NAMESPACE_ONLY, DENY_ALL, INTERNET_ONLY
egressNetworkPolicy String @default("ALLOW_ALL") // ALLOW_ALL, NAMESPACE_ONLY, DENY_ALL, INTERNET_ONLY
useNetworkPolicy Boolean @default(true)
appDomains AppDomain[]
appPorts AppPort[]

View File

@@ -17,7 +17,7 @@ interface ProjectNetworkGraphProps {
apps: AppWithRelations[];
}
const PolicyIcon = ({ policy, type, ports }: { policy: string, type: 'ingress' | 'egress', ports: string }) => {
const PolicyIcon = ({ policy, type, ports, useNetworkPolicy }: { policy: string, type: 'ingress' | 'egress', ports: string, useNetworkPolicy: boolean }) => {
let Icon = Globe;
let color = type === 'egress' ? 'text-blue-500' : 'text-green-500';
let title = policy;
@@ -46,16 +46,11 @@ const PolicyIcon = ({ policy, type, ports }: { policy: string, type: 'ingress' |
return (
<div className='flex items-center gap-2'>
{/*<div className={`p-1 bg-white rounded-full border shadow-sm ${color}`} title={`${type}: ${title}`}>
<div className=' flex gap-1 items-center'>
<ArrowDown size={13} className='text-gray-500' />
</div>
</div>*/}
<div className={`p-1 bg-white rounded-full border shadow-sm ${color}`} title={`${type}: ${title}`}>
{useNetworkPolicy && <div className={`p-1 bg-white rounded-full border shadow-sm ${color}`} title={`${type}: ${title}`}>
<div className=' flex gap-1 items-center'>
<Icon size={16} />
</div>
</div>
</div>}
{ports && type === 'ingress' && <div className={`p-1 px-2 bg-white rounded-full border shadow-sm text-xs text-gray-500`} title={`${type}: ${title}`}>
{ports}
</div>}
@@ -69,7 +64,7 @@ const AppNode = ({ data }: { data: any }) => {
<Handle type="target" position={Position.Top} className="!bg-transparent !border-0" />
<div className="absolute -top-3 left-1/2 -translate-x-1/2 z-10">
<PolicyIcon policy={data.ingressPolicy} ports={data.ports} type="ingress" />
<PolicyIcon policy={data.ingressPolicy} ports={data.ports} useNetworkPolicy={data.app.useNetworkPolicy} type="ingress" />
</div>
<div className="font-semibold text-sm mt-2 mb-2 flex gap-3 items-center justify-center">
@@ -77,7 +72,7 @@ const AppNode = ({ data }: { data: any }) => {
</div>
<div className="absolute -bottom-3 left-1/2 -translate-x-1/2 z-10">
<PolicyIcon policy={data.egressPolicy} ports={data.ports} type="egress" />
<PolicyIcon policy={data.egressPolicy} ports={data.ports} useNetworkPolicy={data.app.useNetworkPolicy} type="egress" />
</div>
<Handle type="source" position={Position.Bottom} className="!bg-transparent !border-0" />
@@ -176,6 +171,7 @@ export default function ProjectNetworkGraph({ apps }: ProjectNetworkGraphProps)
ingressPolicy: app.ingressNetworkPolicy,
egressPolicy: app.egressNetworkPolicy,
appId: app.id,
app,
ports
},
type: 'appNode',

View File

@@ -26,7 +26,7 @@ export const deleteBasicAuth = async (basicAuthId: string) =>
return new SuccessActionResult(undefined, 'Successfully deleted item');
});
export const saveNetworkPolicy = async (appId: string, ingressPolicy: string, egressPolicy: string) =>
export const saveNetworkPolicy = async (appId: string, ingressPolicy: string, egressPolicy: string, useNetworkPolicy: boolean) =>
simpleAction(async () => {
await isAuthorizedWriteForApp(appId);
@@ -38,7 +38,8 @@ export const saveNetworkPolicy = async (appId: string, ingressPolicy: string, eg
await appService.save({
...app,
ingressNetworkPolicy: ingressPolicy,
egressNetworkPolicy: egressPolicy
egressNetworkPolicy: egressPolicy,
useNetworkPolicy: useNetworkPolicy
});
return new SuccessActionResult(undefined, 'Network policy saved');
});

View File

@@ -9,7 +9,9 @@ import { useState } from "react";
import { Toast } from "@/frontend/utils/toast.utils";
import { saveNetworkPolicy } from "./actions";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { HelpCircle } from "lucide-react";
import { HelpCircle, AlertTriangle } from "lucide-react";
import { Switch } from "@/components/ui/switch";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
export default function NetworkPolicy({ app, readonly }: {
app: AppExtendedModel;
@@ -17,10 +19,11 @@ export default function NetworkPolicy({ app, readonly }: {
}) {
const [ingressPolicy, setIngressPolicy] = useState(app.ingressNetworkPolicy);
const [egressPolicy, setEgressPolicy] = useState(app.egressNetworkPolicy);
const [useNetworkPolicy, setUseNetworkPolicy] = useState(app.useNetworkPolicy);
const [showHelp, setShowHelp] = useState(false);
const handleSave = async () => {
await Toast.fromAction(() => saveNetworkPolicy(app.id, ingressPolicy, egressPolicy));
await Toast.fromAction(() => saveNetworkPolicy(app.id, ingressPolicy, egressPolicy, useNetworkPolicy));
};
return (
@@ -34,11 +37,38 @@ export default function NetworkPolicy({ app, readonly }: {
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between space-x-2 p-4 border rounded-lg">
<div className="space-y-0.5">
<Label htmlFor="use-network-policy">Enable Network Policies</Label>
<p className="text-sm text-muted-foreground">
Control whether network policies are applied to this application
</p>
</div>
<Switch
id="use-network-policy"
disabled={readonly}
checked={useNetworkPolicy}
onCheckedChange={setUseNetworkPolicy}
/>
</div>
{!useNetworkPolicy && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
Disabling network policies removes all network traffic restrictions for this application.
This may expose your application to unauthorized access and security risks.
Only disable this if you fully understand the security implications.
</AlertDescription>
</Alert>
)}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="ingress">Ingress Policy (Incoming Traffic)</Label>
<Select
disabled={readonly}
disabled={readonly || !useNetworkPolicy}
value={ingressPolicy}
onValueChange={setIngressPolicy}
>
@@ -59,7 +89,7 @@ export default function NetworkPolicy({ app, readonly }: {
<div className="space-y-2">
<Label htmlFor="egress">Egress Policy (Outgoing Traffic)</Label>
<Select
disabled={readonly}
disabled={readonly || !useNetworkPolicy}
value={egressPolicy}
onValueChange={setEgressPolicy}
>

View File

@@ -11,6 +11,12 @@ class NetworkPolicyService {
const policyName = KubeObjectNameUtils.toNetworkPolicyName(app.id);
const namespace = app.projectId;
// If network policies are disabled, delete existing policy if any and return
if (!app.useNetworkPolicy) {
await this.deleteNetworkPolicy(app.id, app.projectId);
return;
}
const ingressPolicy = this.normalizePolicy(app.ingressNetworkPolicy);
const egressPolicy = this.normalizePolicy(app.egressNetworkPolicy);

View File

@@ -25,6 +25,7 @@ export const AppModel = z.object({
webhookId: z.string().nullish(),
ingressNetworkPolicy: z.string(),
egressNetworkPolicy: z.string(),
useNetworkPolicy: z.boolean(),
createdAt: z.date(),
updatedAt: z.date(),
})

View File

@@ -41,6 +41,7 @@ export const wordpressAppTemplate: AppTemplateModel = {
envVars: `MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
`,
useNetworkPolicy: true,
},
appDomains: [],
appVolumes: [{
@@ -78,6 +79,7 @@ WORDPRESS_DB_USER={username}
WORDPRESS_DB_PASSWORD={password}
WORDPRESS_TABLE_PREFIX=wp_
`,
useNetworkPolicy: true,
},
appDomains: [],
appVolumes: [{

View File

@@ -51,6 +51,7 @@ export const mariadbAppTemplate: AppTemplateModel = {
egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES,
replicas: 1,
envVars: ``,
useNetworkPolicy: true,
},
appDomains: [],
appVolumes: [{

View File

@@ -44,6 +44,7 @@ export const mongodbAppTemplate: AppTemplateModel = {
egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES,
replicas: 1,
envVars: ``,
useNetworkPolicy: true,
},
appDomains: [],
appVolumes: [{

View File

@@ -51,6 +51,7 @@ export const mysqlAppTemplate: AppTemplateModel = {
envVars: ``,
ingressNetworkPolicy: Constants.DEFAULT_INGRESS_NETWORK_POLICY_DATABASES,
egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES,
useNetworkPolicy: true,
},
appDomains: [],
appVolumes: [{

View File

@@ -45,6 +45,7 @@ export const postgreAppTemplate: AppTemplateModel = {
egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES,
envVars: `PGDATA=/var/lib/qs-postgres/data
`,
useNetworkPolicy: true,
},
appDomains: [],
appVolumes: [{