Skip to content

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:

terraform/cluster/aws/variables.tf
# 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):

terraform/cluster/aws/aws.tfvars
# Restrict to your public IP
# Find your IP: curl -s ifconfig.me
allowed_admin_cidrs = [
  "196.45.28.20/32",
]

Other security variables:

terraform/cluster/aws/variables.tf
# 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:

terraform/modules/aws/network/security_groups.tf
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):

terraform/modules/aws/network/security_groups.tf
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):

terraform/modules/aws/network/security_groups.tf
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:

terraform/cluster/aws/main.tf
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:

terraform/modules/aws/network/security_groups.tf
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:

terraform/modules/aws/network/outputs.tf
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.

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

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:

  1. Set firewall = true on the network device (modify the VM module)
  2. Create Proxmox firewall rules allowing all the ports listed in the Requirements section above
  3. 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:

# In envs/proxmox.tfvars
network_vlan_id = 30    # Place VMs on VLAN 30

This provides Layer 2 isolation between the Kubernetes cluster and other workloads on the same Proxmox host.