diff --git a/GLOSSARY.md b/GLOSSARY.md
index d9131eb4..a47a3084 100644
--- a/GLOSSARY.md
+++ b/GLOSSARY.md
@@ -77,7 +77,7 @@ _Avoid_: advanced configuration, app firewall mode
In an App Extended **Full Schema Write**, the App Network Policy Configuration is provided explicitly as either its complete value or `null`; omitting it is invalid.
**Agent Network Policy Configuration**:
-The active egress network-policy configuration for an **Agent**, consisting of optional Internet access and detailed target-App rules.
+The active egress network-policy configuration for an **Agent**, consisting of optional Internet access, optional **Traefik Access**, and detailed target-App rules.
_Avoid_: agent firewall, app network-policy configuration
In an Agent Extended **Full Schema Write**, the Agent Network Policy Configuration is provided explicitly as either its complete value or `null`; omitting it is invalid.
@@ -114,6 +114,10 @@ _Avoid_: policy migration, extended upgrade
A configured domain that exposes an **App** through Traefik.
_Avoid_: public app address, ingress rule
+**Traefik Access**:
+The network-policy permission that lets a workload reach other Apps and Agents through the cluster's Traefik ingress, including through their App Domains and Agent Domains.
+_Avoid_: Internet access, cluster egress, public access
+
**Project Network Graph**:
A visual representation of the effective traffic routes between the Apps and Agents visible from a **Project**, including Traefik ingress permitted by each **App Domain** even when that App has Network Policies disabled.
_Avoid_: network-policy-only graph, deployment topology
@@ -410,6 +414,9 @@ _Avoid_: manual initial port requirement
- A **Network Policy Mode Migration** replaces an App's active **Simple App Network Policy Configuration** with its equivalent **Extended App Network Policy Configuration**.
- An **Extended App Network Policy Configuration** can have zero or more **App Network Policy Rules** for ingress and zero or more for egress.
- An **App Domain** permits Traefik ingress to its App when Network Policies are active.
+- **Traefik Access** is independent of Internet access.
+- A workload with **Traefik Access** enabled can reach another workload through its **App Domain** or Agent Domain even when Internet access is disabled.
+- **Traefik Access** only applies while Network Policies are active.
- An extended ingress rule identifies an **App Network Policy Rule Source**; an extended egress rule identifies an **App Network Policy Rule Target**.
- An **App Network Policy Rule** may reference an App in another project only when its user has a **Workload Permission** for that App.
- A **Configured Source** belongs to exactly one **App**.
diff --git a/docs/adr/0016-traefik-access-egress.md b/docs/adr/0016-traefik-access-egress.md
new file mode 100644
index 00000000..fb9a5286
--- /dev/null
+++ b/docs/adr/0016-traefik-access-egress.md
@@ -0,0 +1,5 @@
+# Allow Traefik egress as a separate Network Policy flag
+
+Apps and Agents must be able to reach other workloads through their App and Agent Domains, but the Internet Access egress rule excludes the cluster's private CIDRs. Traffic that reaches a node's public ingress address is translated to a Traefik Pod inside a private CIDR and is therefore denied. We add an independent `allowTraefikAccess` flag to the App and Agent Network Policy Configurations that permits egress to the Traefik Pods in `kube-system`, independent of Internet access. It defaults to `false` to preserve existing isolation.
+
+The rule targets the Traefik Pods without port restrictions. Reusing Internet access would widen egress to every private cluster destination, while selecting the Traefik Service IP is not expressible as a Network Policy peer. Explicitly allowing ports `80`/`443` would also fail because the k3s Traefik Pods listen on `8000`/`8443`; Network Policy matches the translated Pod port. Restricting the destination to the Traefik Pods keeps the exception narrowly scoped while covering HTTP and HTTPS entrypoints, including custom ones.
diff --git a/prisma/migrations/20261110120000_add_allow_traefik_access/migration.sql b/prisma/migrations/20261110120000_add_allow_traefik_access/migration.sql
new file mode 100644
index 00000000..38e0a187
--- /dev/null
+++ b/prisma/migrations/20261110120000_add_allow_traefik_access/migration.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "AppNetworkPolicy" ADD COLUMN "allowTraefikAccess" BOOLEAN NOT NULL DEFAULT false;
+ALTER TABLE "AgentNetworkPolicy" ADD COLUMN "allowTraefikAccess" BOOLEAN NOT NULL DEFAULT false;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 9452d17b..80c07ca0 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -319,6 +319,7 @@ model AgentNetworkPolicy {
agentId String @unique
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
allowInternetAccess Boolean @default(true)
+ allowTraefikAccess Boolean @default(false)
rules AgentNetworkPolicyRule[]
@@ -484,6 +485,7 @@ model AppNetworkPolicy {
appId String @unique
app App @relation(fields: [appId], references: [id], onDelete: Cascade)
allowInternetAccess Boolean @default(true)
+ allowTraefikAccess Boolean @default(false)
rules AppNetworkPolicyRule[]
diff --git a/src/__tests__/integration/server/api/v1/subitem-identity.integration.spec.ts b/src/__tests__/integration/server/api/v1/subitem-identity.integration.spec.ts
index 8e36545b..d76206b7 100644
--- a/src/__tests__/integration/server/api/v1/subitem-identity.integration.spec.ts
+++ b/src/__tests__/integration/server/api/v1/subitem-identity.integration.spec.ts
@@ -503,6 +503,7 @@ function createAgentPayload(
agentFileMounts: [{ containerMountPath: '/workspace/config.json', content: '{}' }],
agentNetworkPolicy: {
allowInternetAccess: true,
+ allowTraefikAccess: false,
rules: [{ type: 'EGRESS', targetAppId, port: 443, protocol: 'TCP' }],
},
};
diff --git a/src/__tests__/integration/server/services/agent.service.integration.spec.ts b/src/__tests__/integration/server/services/agent.service.integration.spec.ts
index e589fa90..7cca9a43 100644
--- a/src/__tests__/integration/server/services/agent.service.integration.spec.ts
+++ b/src/__tests__/integration/server/services/agent.service.integration.spec.ts
@@ -124,6 +124,7 @@ describe('agent.service integration - saveAgentExtendedModel', () => {
agentFileMounts: [{ containerMountPath: '/fresh.txt', content: 'fresh' } as any],
agentNetworkPolicy: {
allowInternetAccess: false,
+ allowTraefikAccess: true,
rules: [{ targetAppId: targetAppIdB, port: 8443, protocol: 'TCP' }],
} as any,
});
@@ -153,5 +154,6 @@ describe('agent.service integration - saveAgentExtendedModel', () => {
expect(rules[0].id).not.toBe(staleRule.id);
expect(refreshedPolicy.allowInternetAccess).toBe(false);
+ expect(refreshedPolicy.allowTraefikAccess).toBe(true);
});
});
diff --git a/src/__tests__/integration/server/services/app.service.integration.spec.ts b/src/__tests__/integration/server/services/app.service.integration.spec.ts
index 1947c2b8..d725850b 100644
--- a/src/__tests__/integration/server/services/app.service.integration.spec.ts
+++ b/src/__tests__/integration/server/services/app.service.integration.spec.ts
@@ -157,6 +157,7 @@ describe('app.service integration - subitem ownership guards', () => {
id: sourceApp.id,
appNetworkPolicy: {
allowInternetAccess: false,
+ allowTraefikAccess: true,
rules: [{
id: rule.id,
targetAppId: targetApp.id,
@@ -170,6 +171,7 @@ describe('app.service integration - subitem ownership guards', () => {
expect(savedApp.appNetworkPolicy).toMatchObject({
allowInternetAccess: false,
+ allowTraefikAccess: true,
rules: [expect.objectContaining({ id: rule.id, port: 8443 })],
});
});
@@ -182,6 +184,7 @@ describe('app.service integration - subitem ownership guards', () => {
id: sourceApp.id,
appNetworkPolicy: {
allowInternetAccess: true,
+ allowTraefikAccess: false,
rules: [{
targetAppId: sourceApp.id,
targetAgentId: null,
@@ -208,6 +211,7 @@ describe('app.service integration - subitem ownership guards', () => {
id: sourceApp.id,
appNetworkPolicy: {
allowInternetAccess: true,
+ allowTraefikAccess: false,
rules: [rule, rule],
},
})).rejects.toThrow('A matching network policy rule already exists.');
diff --git a/src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx b/src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx
index 82c8f4da..5fad60a9 100644
--- a/src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx
+++ b/src/app/project/[projectId]/agents/details/network/agent-network-policy-card.tsx
@@ -26,6 +26,7 @@ export default function AgentNetworkPolicyCard({ agent, readonly }: {
const { openDialog } = useDialog();
const rules = agent.agentNetworkPolicy?.rules ?? [];
const [allowInternetAccess, setAllowInternetAccess] = useState(agent.agentNetworkPolicy?.allowInternetAccess !== false);
+ const [allowTraefikAccess, setAllowTraefikAccess] = useState(agent.agentNetworkPolicy?.allowTraefikAccess === true);
const handleAllowInternetAccessChange = async (checked: boolean) => {
setAllowInternetAccess(checked);
@@ -40,6 +41,19 @@ export default function AgentNetworkPolicyCard({ agent, readonly }: {
}
};
+ const handleAllowTraefikAccessChange = async (checked: boolean) => {
+ setAllowTraefikAccess(checked);
+ try {
+ await Toast.fromAction(() => saveAgentNetworkPolicySettings(
+ undefined,
+ { allowTraefikAccess: checked },
+ agent.id,
+ ), 'Successfully updated Traefik access setting.');
+ } catch {
+ setAllowTraefikAccess(!checked);
+ }
+ };
+
const asyncDeleteRule = async (ruleId: string) => {
const confirm = await openConfirmDialog({
title: "Delete Egress Rule",
@@ -70,7 +84,7 @@ export default function AgentNetworkPolicyCard({ agent, readonly }: {
Network Policy
- Control whether this agent has general internet access and define specific egress rules to other apps.
+ Control whether this agent has general internet and Traefik access, and define specific egress rules to other apps.
@@ -89,6 +103,21 @@ export default function AgentNetworkPolicyCard({ agent, readonly }: {
/>
+
+
+
+
+ Control whether this agent can reach apps and agents exposed through this cluster's Traefik ingress.
+