From 12c01e4e0c1c92aaf42402bac647574325d6c05b Mon Sep 17 00:00:00 2001 From: biersoeckli Date: Mon, 22 Dec 2025 13:32:56 +0000 Subject: [PATCH] feat: implement useNetworkPolicy flag for applications and update related components --- .../20251222131551_migration/migration.sql | 36 ++++++++++++++++++ prisma/schema.prisma | 5 ++- .../[projectId]/project-network-graph.tsx | 16 +++----- .../project/app/[appId]/advanced/actions.ts | 5 ++- .../app/[appId]/advanced/network-policy.tsx | 38 +++++++++++++++++-- src/server/services/network-policy.service.ts | 6 +++ src/shared/model/generated-zod/app.ts | 1 + .../templates/apps/wordpress.template.ts | 2 + .../templates/databases/mariadb.template.ts | 1 + .../templates/databases/mongodb.template.ts | 1 + .../templates/databases/mysql.template.ts | 1 + .../templates/databases/postgres.template.ts | 1 + 12 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 prisma/migrations/20251222131551_migration/migration.sql diff --git a/prisma/migrations/20251222131551_migration/migration.sql b/prisma/migrations/20251222131551_migration/migration.sql new file mode 100644 index 0000000..d421176 --- /dev/null +++ b/prisma/migrations/20251222131551_migration/migration.sql @@ -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; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5320fc8..f440dea 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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[] diff --git a/src/app/project/[projectId]/project-network-graph.tsx b/src/app/project/[projectId]/project-network-graph.tsx index d84c546..1871190 100644 --- a/src/app/project/[projectId]/project-network-graph.tsx +++ b/src/app/project/[projectId]/project-network-graph.tsx @@ -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 (
- {/*
-
- -
-
*/} -
+ {useNetworkPolicy &&
-
+
} {ports && type === 'ingress' &&
{ports}
} @@ -69,7 +64,7 @@ const AppNode = ({ data }: { data: any }) => {
- +
@@ -77,7 +72,7 @@ const AppNode = ({ data }: { data: any }) => {
- +
@@ -176,6 +171,7 @@ export default function ProjectNetworkGraph({ apps }: ProjectNetworkGraphProps) ingressPolicy: app.ingressNetworkPolicy, egressPolicy: app.egressNetworkPolicy, appId: app.id, + app, ports }, type: 'appNode', diff --git a/src/app/project/app/[appId]/advanced/actions.ts b/src/app/project/app/[appId]/advanced/actions.ts index 0ffd5c4..645d5eb 100644 --- a/src/app/project/app/[appId]/advanced/actions.ts +++ b/src/app/project/app/[appId]/advanced/actions.ts @@ -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'); }); diff --git a/src/app/project/app/[appId]/advanced/network-policy.tsx b/src/app/project/app/[appId]/advanced/network-policy.tsx index dc5ae4c..3107106 100644 --- a/src/app/project/app/[appId]/advanced/network-policy.tsx +++ b/src/app/project/app/[appId]/advanced/network-policy.tsx @@ -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 }: { +
+
+ +

+ Control whether network policies are applied to this application +

+
+ +
+ + {!useNetworkPolicy && ( + + + Warning + + 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. + + + )} +
diff --git a/src/server/services/network-policy.service.ts b/src/server/services/network-policy.service.ts index de622ec..9445e37 100644 --- a/src/server/services/network-policy.service.ts +++ b/src/server/services/network-policy.service.ts @@ -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); diff --git a/src/shared/model/generated-zod/app.ts b/src/shared/model/generated-zod/app.ts index 17917ac..b5cac27 100644 --- a/src/shared/model/generated-zod/app.ts +++ b/src/shared/model/generated-zod/app.ts @@ -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(), }) diff --git a/src/shared/templates/apps/wordpress.template.ts b/src/shared/templates/apps/wordpress.template.ts index 01d381a..7ceb01d 100644 --- a/src/shared/templates/apps/wordpress.template.ts +++ b/src/shared/templates/apps/wordpress.template.ts @@ -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: [{ diff --git a/src/shared/templates/databases/mariadb.template.ts b/src/shared/templates/databases/mariadb.template.ts index 0cdffe2..9aa2d56 100644 --- a/src/shared/templates/databases/mariadb.template.ts +++ b/src/shared/templates/databases/mariadb.template.ts @@ -51,6 +51,7 @@ export const mariadbAppTemplate: AppTemplateModel = { egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES, replicas: 1, envVars: ``, + useNetworkPolicy: true, }, appDomains: [], appVolumes: [{ diff --git a/src/shared/templates/databases/mongodb.template.ts b/src/shared/templates/databases/mongodb.template.ts index 5e68cb4..0a49d24 100644 --- a/src/shared/templates/databases/mongodb.template.ts +++ b/src/shared/templates/databases/mongodb.template.ts @@ -44,6 +44,7 @@ export const mongodbAppTemplate: AppTemplateModel = { egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES, replicas: 1, envVars: ``, + useNetworkPolicy: true, }, appDomains: [], appVolumes: [{ diff --git a/src/shared/templates/databases/mysql.template.ts b/src/shared/templates/databases/mysql.template.ts index 3d7f8df..aa9c22d 100644 --- a/src/shared/templates/databases/mysql.template.ts +++ b/src/shared/templates/databases/mysql.template.ts @@ -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: [{ diff --git a/src/shared/templates/databases/postgres.template.ts b/src/shared/templates/databases/postgres.template.ts index 79d172e..353836d 100644 --- a/src/shared/templates/databases/postgres.template.ts +++ b/src/shared/templates/databases/postgres.template.ts @@ -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: [{