mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-10 13:39:07 -06:00
feat: implement useNetworkPolicy flag for applications and update related components
This commit is contained in:
36
prisma/migrations/20251222131551_migration/migration.sql
Normal file
36
prisma/migrations/20251222131551_migration/migration.sql
Normal 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;
|
||||
@@ -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[]
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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: [{
|
||||
|
||||
@@ -51,6 +51,7 @@ export const mariadbAppTemplate: AppTemplateModel = {
|
||||
egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES,
|
||||
replicas: 1,
|
||||
envVars: ``,
|
||||
useNetworkPolicy: true,
|
||||
},
|
||||
appDomains: [],
|
||||
appVolumes: [{
|
||||
|
||||
@@ -44,6 +44,7 @@ export const mongodbAppTemplate: AppTemplateModel = {
|
||||
egressNetworkPolicy: Constants.DEFAULT_EGRESS_NETWORK_POLICY_DATABASES,
|
||||
replicas: 1,
|
||||
envVars: ``,
|
||||
useNetworkPolicy: true,
|
||||
},
|
||||
appDomains: [],
|
||||
appVolumes: [{
|
||||
|
||||
@@ -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: [{
|
||||
|
||||
@@ -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: [{
|
||||
|
||||
Reference in New Issue
Block a user