Skip to content

3.2 Configure Firewall Rules

Firewall rules are implemented as AWS Security Groups, defined in modules/security_groups.pkl. This module 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 composed into main.pkl and deployed as part of formae apply.

Step 1: Configure Security Variables

Open vars.pkl (or your environment override) and review the security-related variables:

formae/cluster/aws/vars.pkl
/// CIDR range allowed to reach the K8s API (6443) and Talos API (50000)
/// from outside the VPC. Set to a specific IP (e.g. "41.x.x.x/32") to
/// restrict access, or null to allow only VPC-internal access.
allowedAdminCidr: String? = null

To allow external access (e.g. from your workstation):

formae/cluster/aws/envs/demo.pkl
// In envs/demo.pkl — restrict to your public IP
// Find your IP: curl -s ifconfig.me
allowedAdminCidr = "196.45.28.20/32"

Other security variables:

formae/cluster/aws/vars.pkl
/// Container Network Interface plugin (cilium or calico).
cniType: String = "cilium"

/// Open NodePort range (30000-32767) in worker security groups.
enableNodePort: Boolean = false

Step 2: Understand the Module

The module is at modules/security_groups.pkl. It imports the network module to reference the VPC ID and CIDR:

formae/cluster/aws/modules/security_groups.pkl
import "./network.pkl"

Security Group Definitions

Two security groups are created, both attached to the VPC:

formae/cluster/aws/modules/security_groups.pkl
local controlPlaneSg = new sg.SecurityGroup {
  label = "\(vars.environment)-talos-control-plane-sg"
  groupName = "\(vars.environment)-talos-control-plane-sg"
  groupDescription = "Security group for Talos control plane nodes"
  vpcId = network.vpcId
  tags = makeTags(new Mapping {
    ["Name"] = "\(vars.environment)-talos-control-plane-sg"
    ["NodeType"] = "control-plane"
  })
}

local workerSg = new sg.SecurityGroup {
  label = "\(vars.environment)-talos-worker-sg"
  groupName = "\(vars.environment)-talos-worker-sg"
  groupDescription = "Security group for Talos worker nodes"
  vpcId = network.vpcId
  tags = makeTags(new Mapping {
    ["Name"] = "\(vars.environment)-talos-worker-sg"
    ["NodeType"] = "worker"
  })
}

Ingress Rule Pattern

Each rule is a separate SecurityGroupIngress resource that references its parent security group via groupId. The source can be a CIDR block or another security group:

CIDR-based rule (allows traffic from anywhere in the VPC):

formae/cluster/aws/modules/security_groups.pkl
local cpK8sApi = new sgIngress.SecurityGroupIngress {
  label = "cp-k8s-api"
  groupId = controlPlaneSg.res.groupId
  description = "Kubernetes API server"
  fromPort = 6443
  toPort = 6443
  ipProtocol = "tcp"
  cidrIp = network.vpcCidr                  // e.g. "10.0.0.0/16"
}

Security group-based rule (allows traffic from a specific node type):

formae/cluster/aws/modules/security_groups.pkl
local cpEtcd = new sgIngress.SecurityGroupIngress {
  label = "cp-etcd"
  groupId = controlPlaneSg.res.groupId
  description = "etcd peer communication"
  fromPort = 2379
  toPort = 2380
  ipProtocol = "tcp"
  sourceSecurityGroupId = controlPlaneSg.res.groupId   // CP-to-CP only
}

Conditional External Access Rules

When allowedAdminCidr is set, two additional rules are created for external access to the control plane:

formae/cluster/aws/modules/security_groups.pkl
local cpK8sApiExternal = if (vars.allowedAdminCidr != null)
  new sgIngress.SecurityGroupIngress {
    label = "cp-k8s-api-external"
    groupId = controlPlaneSg.res.groupId
    description = "Kubernetes API (external admin)"
    fromPort = 6443
    toPort = 6443
    ipProtocol = "tcp"
    cidrIp = vars.allowedAdminCidr           // e.g. "196.45.28.20/32"
  }
  else null

When allowedAdminCidr = null, these rules are not 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 allowedAdminCidr Kubernetes API (external) — conditional
cp-talos-api-external 50000 TCP allowedAdminCidr 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:

formae/cluster/aws/modules/security_groups.pkl
local cpEgressAll = new sgEgress.SecurityGroupEgress {
  label = "cp-egress-all"
  groupId = controlPlaneSg.res.groupId
  description = "Allow all outbound traffic"
  ipProtocol = "-1"
  cidrIp = "0.0.0.0/0"
}

Step 3: Module Exports

formae/cluster/aws/modules/security_groups.pkl
/// Control plane security group ID for use by compute module.
controlPlaneSecurityGroupId = controlPlaneSg.res.groupId

/// Worker security group ID for use by compute module.
workerSecurityGroupId = workerSg.res.groupId
Export Consumed By
controlPlaneSecurityGroupId compute.pkl (CP instances)
workerSecurityGroupId compute.pkl (worker instances)

Customisation Summary

What to Change Where Variable
Allow external admin access vars.pkl allowedAdminCidr = "x.x.x.x/32"
Restrict to VPC only vars.pkl allowedAdminCidr = null
Enable NodePort range on workers vars.pkl enableNodePort = true
Change CNI type vars.pkl cniType = "calico" (Cilium rules would need updating)