feat: enhance network policy with INTERNET_ONLY options and update related components

This commit is contained in:
biersoeckli
2025-12-16 14:25:43 +00:00
parent 06e31463f3
commit 8addee3bdd
10 changed files with 113 additions and 29 deletions

View File

@@ -200,8 +200,8 @@ model App {
webhookId String?
ingressNetworkPolicy String @default("ALLOW_ALL") // ALLOW_ALL, NAMESPACE_ONLY, DENY_ALL
egressNetworkPolicy String @default("ALLOW_ALL") // ALLOW_ALL, NAMESPACE_ONLY, DENY_ALL
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
appDomains AppDomain[]
appPorts AppPort[]

View File

@@ -39,7 +39,7 @@ export default function BasicAuth({ app, readonly }: {
</CardHeader>
<CardContent>
<Table>
<TableCaption>{app.appFileMounts.length} Auth Credentials</TableCaption>
<TableCaption>{app.appBasicAuths.length} Auth Credentials</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Username</TableHead>

View File

@@ -44,6 +44,7 @@ export default function NetworkPolicy({ app, readonly }: {
</SelectTrigger>
<SelectContent>
<SelectItem value="ALLOW_ALL">Allow All (Internet + Project Apps)</SelectItem>
<SelectItem value="INTERNET_ONLY">Internet Only</SelectItem>
<SelectItem value="NAMESPACE_ONLY">Project Apps Only</SelectItem>
<SelectItem value="DENY_ALL">Deny All</SelectItem>
</SelectContent>
@@ -64,6 +65,7 @@ export default function NetworkPolicy({ app, readonly }: {
</SelectTrigger>
<SelectContent>
<SelectItem value="ALLOW_ALL">Allow All (Internet + Project Apps)</SelectItem>
<SelectItem value="INTERNET_ONLY">Internet Only</SelectItem>
<SelectItem value="NAMESPACE_ONLY">Project Apps Only</SelectItem>
<SelectItem value="DENY_ALL">Deny All</SelectItem>
</SelectContent>

View File

@@ -20,7 +20,7 @@ export default async function RootLayout({
const session = await isAuthorizedReadForApp(appId);
const app = await appService.getExtendedById(appId);
const showIngressWarning = app.appDomains.length > 0 && app.ingressNetworkPolicy !== 'ALLOW_ALL';
const showIngressWarning = app.appDomains.length > 0 && app.ingressNetworkPolicy !== 'ALLOW_ALL' && app.ingressNetworkPolicy !== 'INTERNET_ONLY';
return (
<div className="flex-1 space-y-6 pt-6">
@@ -28,16 +28,16 @@ export default async function RootLayout({
title={app.name}
subtitle={`App ID: ${app.id}`}>
</PageTitle>
{showIngressWarning && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
You have configured domains for this app, but the Ingress Network Policy is not set to &quot;Allow All&quot;.
External traffic via the domain might be blocked.
</AlertDescription>
</Alert>
)}
{showIngressWarning && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
You have configured domains for this app, but the Ingress Network Policy is not set to &quot;Allow All&quot; or &quot;Internet Only&quot;.
External traffic via the domain might be blocked.
</AlertDescription>
</Alert>
)}
<AppActionButtons session={session} app={app} />
{children}
</div>

View File

@@ -45,11 +45,73 @@ class NetworkPolicyService {
if (policyType === 'ALLOW_ALL') {
// Allow from everywhere
rules.push({
from: [{
ipBlock: {
cidr: '0.0.0.0/0'
from: [
{
ipBlock: {
cidr: '0.0.0.0/0',
except: [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8'
]
}
},
{
// Allow from Traefik ingress controller
namespaceSelector: {
matchLabels: {
'kubernetes.io/metadata.name': 'kube-system'
}
},
podSelector: {
matchLabels: {
'app.kubernetes.io/name': 'traefik'
}
}
},
{
podSelector: {} // Selects all pods in the same namespace
}
}]
]
});
} else if (policyType === 'INTERNET_ONLY') {
// Allow from internet (external to cluster) and from Traefik ingress controller
// Block other internal pod traffic
rules.push({
from: [
{
ipBlock: {
cidr: '0.0.0.0/0',
except: [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8'
]
}
},
{
// Allow from Traefik ingress controller
namespaceSelector: {
matchLabels: {
'kubernetes.io/metadata.name': 'kube-system'
}
},
podSelector: {
matchLabels: {
'app.kubernetes.io/name': 'traefik'
}
}
},
{
podSelector: {
matchLabels: {
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB
}
}
}
]
});
} else if (policyType === 'NAMESPACE_ONLY') {
// Allow only from same namespace
@@ -59,7 +121,16 @@ class NetworkPolicyService {
}]
});
} else if (policyType === 'DENY_ALL') {
// No rules means deny all
// No rules means deny all --> except the separate container for database backups
rules.push({
from: [{
podSelector: {
matchLabels: {
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB
}
}
}]
});
}
return rules;
@@ -84,16 +155,7 @@ class NetworkPolicyService {
"k8s-app": "kube-dns"
}
}
},
{
ipBlock: {
cidr: '0.0.0.0/0'
}
}
],
ports: [
{ protocol: 'UDP', port: 53 as any },
{ protocol: 'TCP', port: 53 as any }
]
});
@@ -116,6 +178,21 @@ class NetworkPolicyService {
}
]
});
} else if (policyType === 'INTERNET_ONLY') {
// Allow only to internet, block internal cluster traffic
rules.push({
to: [{
ipBlock: {
cidr: '0.0.0.0/0',
except: [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8'
]
}
}]
});
} else if (policyType === 'NAMESPACE_ONLY') {
// Allow only to same namespace
rules.push({

View File

@@ -42,6 +42,7 @@ class MariaDbBackupService {
annotations: {
[Constants.QS_ANNOTATION_APP_ID]: app.id,
[Constants.QS_ANNOTATION_PROJECT_ID]: app.projectId,
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB
}
},
spec: {

View File

@@ -43,6 +43,7 @@ class MongoDbBackupService {
annotations: {
[Constants.QS_ANNOTATION_APP_ID]: app.id,
[Constants.QS_ANNOTATION_PROJECT_ID]: app.projectId,
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB
}
},
spec: {

View File

@@ -42,6 +42,7 @@ class PostgresBackupService {
annotations: {
[Constants.QS_ANNOTATION_APP_ID]: app.id,
[Constants.QS_ANNOTATION_PROJECT_ID]: app.projectId,
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB
}
},
spec: {

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
export const appNetworkPolicy = z.enum(["ALLOW_ALL", "NAMESPACE_ONLY", "DENY_ALL"]);
export const appNetworkPolicy = z.enum(["ALLOW_ALL", "INTERNET_ONLY", "NAMESPACE_ONLY", "DENY_ALL"]);
export type AppNetworkPolicyType = z.infer<typeof appNetworkPolicy>;

View File

@@ -1,6 +1,8 @@
export class Constants {
static readonly QS_ANNOTATION_APP_ID = 'qs-app-id';
static readonly QS_ANNOTATION_PROJECT_ID = 'qs-project-id';
static readonly QS_ANNOTATION_CONTAINER_TYPE = 'qs-containter-type';
static readonly QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB = 'qs-job-database-backup';
static readonly QS_ANNOTATION_DEPLOYMENT_ID = 'qs-deplyoment-id';
static readonly QS_ANNOTATION_GIT_COMMIT = 'qs-git-commit';
static readonly K3S_JOIN_TOKEN = 'k3sJoinToken';