3.2 Configure Firewall Rules¶
Firewall rules control which traffic can reach your Talos Kubernetes nodes. The port requirements are the same regardless of deployment model — only the implementation differs.
Required Ports¶
All deployment models must allow the following traffic between nodes:
Control Plane Ports¶
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 6443 | TCP | All nodes, admin workstations | Kubernetes API server |
| 50000 | TCP | All nodes, admin workstations | Talos API (apid) |
| 50001 | TCP | Worker nodes | Talos trustd (certificate exchange) |
| 2379-2380 | TCP | Control plane nodes only | etcd peer and client |
| 10250 | TCP | All nodes | Kubelet API |
| 10257 | TCP | Control plane nodes only | kube-controller-manager |
| 10259 | TCP | Control plane nodes only | kube-scheduler |
Worker Ports¶
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 50000 | TCP | Control plane nodes | Talos API |
| 10250 | TCP | All nodes | Kubelet API |
| 30000-32767 | TCP/UDP | As needed | NodePort services (optional) |
CNI Ports (Cilium)¶
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 6081 | UDP | All nodes | GENEVE overlay tunnels |
| 4240 | TCP | All nodes | Cilium health checks |
Egress¶
All nodes require outbound internet access for: - Container image pulls (ghcr.io, docker.io, quay.io) - Talos installer images - NTP time synchronisation - Cluster discovery (unless disabled)
Firewall rules are implemented as AWS Security Groups, defined in terraform/modules/aws/network/security_groups.tf. This file creates two security groups — one for control plane nodes and one for workers — with all the ingress and egress rules required for Talos, Kubernetes, and Cilium to communicate.
You do not run this module independently — it is part of the network module, composed into main.tf and deployed as part of terraform apply.
Step 1: Configure Security Variables¶
Open variables.tf (for definitions) or your .tfvars file (for values) and review the security-related variables:
# CIDR ranges allowed to reach the K8s API (6443) and Talos API (50000)
# from outside the VPC. Empty list = VPC-only access.
variable "allowed_admin_cidrs" {
description = "CIDR ranges allowed to reach K8s API (6443) and Talos API (50000) from outside the VPC"
type = list(string)
default = []
}
To allow external access (e.g. from your workstation):
# Restrict to your public IP
# Find your IP: curl -s ifconfig.me
allowed_admin_cidrs = [
"196.45.28.20/32",
]
Other security variables:
# Container Network Interface plugin (cilium or calico).
variable "cni_type" {
type = string
default = "cilium"
}
# Open NodePort range (30000-32767) in worker security groups.
variable "enable_nodeport" {
type = bool
default = false
}
Step 2: Understand the Module¶
The security groups are defined in terraform/modules/aws/network/security_groups.tf. The groups reference the VPC created in the same network module:
Security Group Definitions¶
Two security groups are created, both attached to the VPC:
resource "aws_security_group" "control_plane" {
name = "${var.environment}-talos-control-plane-sg"
description = "Security group for Talos control plane nodes"
vpc_id = aws_vpc.main.id
tags = merge(var.tags, {
Name = "${var.environment}-talos-control-plane-sg"
NodeType = "control-plane"
})
}
resource "aws_security_group" "worker" {
name = "${var.environment}-talos-worker-sg"
description = "Security group for Talos worker nodes"
vpc_id = aws_vpc.main.id
tags = merge(var.tags, {
Name = "${var.environment}-talos-worker-sg"
NodeType = "worker"
})
}
Ingress Rule Pattern¶
Each rule is a separate aws_vpc_security_group_ingress_rule resource that references its parent security group via security_group_id. The source can be a CIDR block or another security group:
CIDR-based rule (allows traffic from anywhere in the VPC):
resource "aws_vpc_security_group_ingress_rule" "control_plane_k8s_api" {
security_group_id = aws_security_group.control_plane.id
description = "Kubernetes API server"
from_port = 6443
to_port = 6443
ip_protocol = "tcp"
cidr_ipv4 = var.vpc_cidr # e.g. "10.0.0.0/16"
}
Security group-based rule (allows traffic from a specific node type):
resource "aws_vpc_security_group_ingress_rule" "control_plane_etcd" {
security_group_id = aws_security_group.control_plane.id
description = "etcd peer communication"
from_port = 2379
to_port = 2380
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.control_plane.id # CP-to-CP only
}
Conditional External Access Rules¶
When allowed_admin_cidrs is non-empty, additional rules are created in main.tf for external access to the control plane using count:
resource "aws_vpc_security_group_ingress_rule" "cp_k8s_api_external" {
count = length(var.allowed_admin_cidrs)
security_group_id = module.network.control_plane_security_group_id
description = "Kubernetes API (external admin: ${var.allowed_admin_cidrs[count.index]})"
from_port = 6443
to_port = 6443
ip_protocol = "tcp"
cidr_ipv4 = var.allowed_admin_cidrs[count.index]
}
When allowed_admin_cidrs = [], no external rules are created and the control plane is only accessible from within the VPC.
Control Plane Ingress Rules¶
| Rule | Port(s) | Protocol | Source | Purpose |
|---|---|---|---|---|
cp-k8s-api |
6443 | TCP | VPC CIDR | Kubernetes API server |
cp-talos-api |
50000 | TCP | VPC CIDR | Talos API (apid) |
cp-talos-trustd |
50001 | TCP | Worker SG | Talos trustd service |
cp-etcd |
2379-2380 | TCP | CP SG (self) | etcd peer communication |
cp-kubelet-self |
10250 | TCP | CP SG (self) | Kubelet API (CP-to-CP) |
cp-kubelet-vpc |
10250 | TCP | VPC CIDR | Kubelet API (admin access) |
cp-controller-manager |
10257 | TCP | CP SG (self) | kube-controller-manager |
cp-scheduler |
10259 | TCP | CP SG (self) | kube-scheduler |
cp-cilium-geneve-cp |
6081 | UDP | CP SG (self) | Cilium GENEVE overlay (CP-to-CP) |
cp-cilium-geneve-worker |
6081 | UDP | Worker SG | Cilium GENEVE overlay (workers-to-CP) |
cp-cilium-health-cp |
4240 | TCP | CP SG (self) | Cilium health checks (CP-to-CP) |
cp-cilium-health-worker |
4240 | TCP | Worker SG | Cilium health checks (workers-to-CP) |
cp-icmp |
ICMP | ICMP | VPC CIDR | Ping for connectivity validation |
cp_k8s_api_external |
6443 | TCP | allowed_admin_cidrs |
Kubernetes API (external) — conditional |
cp_talos_api_external |
50000 | TCP | allowed_admin_cidrs |
Talos API (external) — conditional |
Worker Ingress Rules¶
| Rule | Port(s) | Protocol | Source | Purpose |
|---|---|---|---|---|
wk-talos-api-cp |
50000 | TCP | CP SG | Talos API from control plane |
wk-talos-api-vpc |
50000 | TCP | VPC CIDR | Talos API (admin access) |
wk-kubelet-cp |
10250 | TCP | CP SG | Kubelet API from control plane |
wk-kubelet-self |
10250 | TCP | Worker SG (self) | Kubelet API (worker-to-worker) |
wk-kubelet-vpc |
10250 | TCP | VPC CIDR | Kubelet API (admin access) |
wk-nodeport-self |
30000-32767 | TCP | Worker SG (self) | NodePort services (worker-to-worker) |
wk-nodeport-vpc |
30000-32767 | TCP | VPC CIDR | NodePort services (admin access) |
wk-cilium-geneve-cp |
6081 | UDP | CP SG | Cilium GENEVE overlay (CP-to-workers) |
wk-cilium-geneve-worker |
6081 | UDP | Worker SG (self) | Cilium GENEVE overlay (worker-to-worker) |
wk-cilium-health-cp |
4240 | TCP | CP SG | Cilium health checks (CP-to-workers) |
wk-cilium-health-worker |
4240 | TCP | Worker SG (self) | Cilium health checks (worker-to-worker) |
wk-icmp |
ICMP | ICMP | VPC CIDR | Ping for connectivity validation |
Egress Rules¶
Both security groups allow all outbound traffic:
resource "aws_vpc_security_group_egress_rule" "control_plane_all" {
security_group_id = aws_security_group.control_plane.id
description = "Allow all outbound traffic"
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}
Step 3: Module Outputs¶
The network module exports the security group IDs via outputs.tf for use by other modules:
output "control_plane_security_group_id" {
description = "Control plane security group ID for use by compute module"
value = aws_security_group.control_plane.id
}
output "worker_security_group_id" {
description = "Worker security group ID for use by compute module"
value = aws_security_group.worker.id
}
| Output | Consumed By |
|---|---|
control_plane_security_group_id |
module.compute (CP instances) |
worker_security_group_id |
module.compute (worker instances) |
Customisation Summary¶
| What to Change | Where | Variable |
|---|---|---|
| Allow external admin access | aws.tfvars |
allowed_admin_cidrs = ["x.x.x.x/32"] |
| Restrict to VPC only | aws.tfvars |
allowed_admin_cidrs = [] |
| Enable NodePort range on workers | aws.tfvars |
enable_nodeport = true |
| Change CNI type | aws.tfvars |
cni_type = "calico" (Cilium rules would need updating) |
Bare metal firewalling is done at the host level using the Talos machine configuration, or at the network level using switch ACLs or a dedicated firewall appliance.
Option 1: Network-Level Firewall (Recommended)¶
If you have a dedicated firewall (pfSense, OPNsense, or similar) or managed switches with ACL support, configure rules at the network level. This is the most maintainable approach.
Example pfSense/OPNsense rules for the Kubernetes VLAN (192.168.30.0/24):
| Action | Source | Destination | Port | Protocol | Description |
|---|---|---|---|---|---|
| Allow | Admin workstation | 192.168.30.0/24 | 6443 | TCP | K8s API access |
| Allow | Admin workstation | 192.168.30.0/24 | 50000 | TCP | Talos API access |
| Allow | 192.168.30.0/24 | 192.168.30.0/24 | Any | Any | Intra-cluster (all node-to-node) |
| Allow | 192.168.30.0/24 | Any | 443 | TCP | Outbound HTTPS (image pulls) |
| Allow | 192.168.30.0/24 | NTP servers | 123 | UDP | Time sync |
| Deny | Any | 192.168.30.0/24 | Any | Any | Default deny inbound |
Option 2: Host-Level Firewall (Talos)¶
Talos supports nftables rules via machine configuration patches. This can be used if you don't have a network firewall, but adds operational complexity.
Note
Talos does not support iptables — it uses nftables. Rules are applied via machine config patches, not via SSH or shell access.
Switch ACLs¶
If your servers connect to managed switches, restrict inter-VLAN traffic:
- Allow the Kubernetes VLAN to reach the internet (outbound NAT)
- Allow the admin VLAN to reach the Kubernetes VLAN on ports 6443, 50000
- Block all other inbound traffic to the Kubernetes VLAN
- Allow all traffic within the Kubernetes VLAN (node-to-node)
Proxmox provides a built-in firewall at both the datacenter and VM level. However, for Talos Kubernetes clusters, the Terraform VM module disables the Proxmox firewall on the network interface (firewall = false), relying on network-level controls instead.
Why the Proxmox Firewall is Disabled¶
The VM module sets firewall = false on the network device:
# From terraform/modules/proxmox/vm/main.tf
network_device {
bridge = var.network_bridge
model = "virtio"
vlan_id = var.network_vlan_id
firewall = false
}
This is intentional because:
- Kubernetes and Cilium manage their own network policies
- The Proxmox firewall would need complex rules that duplicate what Cilium already provides
- Debugging connectivity issues is easier without an extra firewall layer
Recommended Approach¶
Use network-level firewalling (your router, pfSense, or upstream firewall) to control access to the Kubernetes VLAN. The key rules are:
| Direction | Source | Destination | Ports | Purpose |
|---|---|---|---|---|
| Inbound | Admin network | Kubernetes VLAN | 6443, 50000 | API access |
| Inbound | Kubernetes VLAN | Kubernetes VLAN | All | Node-to-node |
| Outbound | Kubernetes VLAN | Internet | 443, 123 | Image pulls, NTP |
| Inbound | Everything else | Kubernetes VLAN | None | Default deny |
Optional: Enable Proxmox Firewall¶
If you prefer to use the Proxmox firewall, you would need to:
- Set
firewall = trueon the network device (modify the VM module) - Create Proxmox firewall rules allowing all the ports listed in the Requirements section above
- Apply rules at the VM or datacenter level via the Proxmox web UI or
pvesh
Warning
Enabling the Proxmox firewall without correct rules will block Talos communication and prevent the cluster from bootstrapping.
VLAN Isolation¶
If your Proxmox node has multiple VLANs, use the network_vlan_id variable to place Kubernetes VMs on a dedicated VLAN:
This provides Layer 2 isolation between the Kubernetes cluster and other workloads on the same Proxmox host.