Networking¶
Charmarr secures network traffic at multiple OSI layers. Each layer addresses a different concern, and together they provide defense in depth.
| Layer | Technology | Purpose |
|---|---|---|
| L2 | VXLAN overlay | External traffic anonymization through VPN |
| L4 | Istio ztunnel | Internal encrypted transport, L4 authorization |
| L7 | Istio ingress gateway | External client ingress and routing |
| L4/L7 | Istio waypoint | Internal L4/L7 authorization |
L2: VXLAN Overlay¶
Privacy-sensitive charms (qBittorrent, SABnzbd, Prowlarr) must not expose your home IP to external services. Charmarr solves this with a VXLAN overlay network that tunnels external traffic through a VPN.
Each privacy-sensitive pod runs a pod-gateway client. This client establishes a VXLAN tunnel to a pod-gateway server running on the Gluetun pod. All external traffic from the pod routes through this tunnel, into the Gluetun pod, and the gluetun container routes this traffic out through a WireGuard VPN connection.
flowchart LR
subgraph Source Pod
SrcPod[Source Pod]
PGC[Pod Gateway Client]
end
subgraph Gluetun Pod
PGS[Pod Gateway Server]
subgraph Gluetun App
WG[WireGuard]
end
end
SrcPod -->|External| PGC
PGC -->|VXLAN| PGS
PGS --> WG
WG -->|Outbound| Internet((Internet))
The VXLAN overlay only captures traffic destined for external networks. Intra-cluster traffic bypasses the overlay entirely and flows through the higher layers (L4/L7) unaffected. This is configured via cluster CIDRs that tell the pod-gateway client which destinations are internal.
A two-way killswitch protects against VPN failures:
- Gluetun firewall: Blocks traffic if the WireGuard connection drops
- NetworkPolicy: Kubernetes blocks traffic if the Gluetun pod dies
Here's an example NetworkPolicy that Charmarr creates for SABnzbd:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: sabnzbd-k8s-vpn-killswitch
namespace: charmarr
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: sabnzbd-k8s
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.1.0.0/16 # Pod CIDR
- to:
- ipBlock:
cidr: 10.152.183.0/24 # Service CIDR
- to:
- ipBlock:
cidr: 192.168.0.0/24 # LAN CIDR
- to:
- ipBlock:
cidr: 169.254.7.127/32 # Pod-gateway server IP
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
This policy only allows SABnzbd to send traffic to:
- Cluster CIDRs (pod, service, LAN): Internal traffic that bypasses the VPN
- Pod-gateway server IP: The entry point into the VXLAN tunnel on the Gluetun pod
- kube-system DNS: Required for name resolution
All other egress is blocked. If the Gluetun pod dies, the pod-gateway server becomes unreachable and SABnzbd cannot reach the internet.
See VPN Gateway for how the gluetun-k8s charm works.
L4/L7: Service Mesh¶
East-west traffic (intra-cluster) flows through Istio ambient mesh. Unlike the VXLAN layer which anonymizes north-south traffic (external), the service mesh encrypts and authorizes internal pod-to-pod communication.
Charmarr uses the Charmed Istio distribution (which I co-maintain for Canonical, shameless plug). The charmed service mesh automatically enrolls Charmarr pods into the mesh and configures authorization policies based on charm topology and policies defined in charm code.
How Cluster Internal Traffic Flows¶
For details, refer to the upstream Istio docs. TL;DR:
When a pod sends traffic to another pod (source and destination may be on the same node):
flowchart LR
subgraph Source Node
SrcPod[Source Pod]
SrcZT[ztunnel]
end
WP[waypoint]
subgraph Destination Node
DstZT[ztunnel]
DstPod[Destination Pod]
end
SrcPod -->|Plain| SrcZT
SrcZT -->|HBONE| WP
WP -->|HBONE| DstZT
DstZT -->|Plain| DstPod
Step 1: Source ztunnel (L4 firewall outlet)
Traffic leaving a pod is redirected to the node's ztunnel. The ztunnel encrypts the traffic using the HBONE protocol, which provides mTLS without the complexity of manually managing certificates. The ztunnel then forwards the encrypted traffic toward the destination.
Step 2: Waypoint (L4/L7 firewall inlet)
The encrypted traffic arrives at the waypoint proxy. The waypoint understands HBONE and can inspect traffic at both L4 and L7. It evaluates authorization policies (firewall rules) and only forwards traffic that matches an explicit allow rule. Traffic without a matching policy is dropped. The difference between ztunnel and waypoint policies is the target type. Understanding this distinction is out of scope for Charmarr docs, refer to the upstream Istio documentation for details.
Step 3: Destination ztunnel (L4 firewall inlet)
The waypoint forwards allowed traffic to the destination node's ztunnel. This ztunnel validates the traffic against its own L4 authorization policies. Traffic without a matching allow policy is dropped.
Step 4: Delivery
The destination ztunnel terminates the HBONE encryption and delivers the traffic to the destination pod as plaintext.
Ingress¶
flowchart LR
Client((Client))
GW[Ingress Gateway]
subgraph Destination Node
DstZT[ztunnel]
DstPod[Destination Pod]
end
Client -->|Inbound| GW
GW -->|HBONE| DstZT
DstZT -->|Plain| DstPod
External client traffic (e.g. a browser accessing Radarr) enters the cluster through the Istio ingress gateway. The gateway evaluates routing rules and forwards traffic to the destination pod.
Traffic from the gateway is captured by the destination node's ztunnel for L4 processing, but the waypoint is bypassed. This is by design: the ingress gateway already operates at L7, so routing traffic through the waypoint would risk double-applying policies like traffic splitting or fault injection.
Authorization Policies¶
The charmed service mesh automatically creates authorization rules based on the system topology and policy targets specified by charms. Unrelated pods cannot communicate.
This limits lateral movement if a pod is compromised. An attacker cannot reach pods that the compromised pod has no legitimate reason to contact.
Traffic Isolation Architecture¶
Charmarr implements defense in depth through orthogonal network layers. The L2 VXLAN overlay handles north-south (external) traffic anonymization, the L4/L7 Istio ambient mesh secures east-west (internal) communication, and the Istio ingress gateway handles inbound client traffic. These layers operate independently with no shared failure modes.
External, internal, and inbound traffic take isolated paths:
flowchart LR
subgraph Source Pod
SrcPod[Source Pod]
PGC[Pod Gateway Client]
end
subgraph Gluetun Pod
PGS[Pod Gateway Server]
subgraph Gluetun App
WG[WireGuard]
end
end
subgraph Istio Ambient
SrcZT[ztunnel]
WP[waypoint]
DstZT[ztunnel]
GW[Ingress Gateway]
end
Dst[Destination Pod]
SrcPod -->|External| PGC
PGC -->|VXLAN| PGS
PGS --> WG
WG -->|Outbound| Internet((Internet))
SrcPod -->|Internal| SrcZT
SrcZT -->|HBONE| WP
WP -->|HBONE| DstZT
DstZT -->|Plain| Dst
Client((Client)) -->|Inbound| GW
GW -->|HBONE| DstZT
This isolation ensures VPN failures don't cascade into internal operations or ingress. When the VPN connection drops or the Gluetun pod goes down, only north-south traffic is blocked. East-west communication and inbound client access remain fully functional. Radarr and Sonarr continue queuing requests to qBittorrent and SABnzbd, and users can still reach the web UIs. Once the VPN is restored, download clients resume fetching from the internet with no manual intervention.