CephCI · Security HardeningSecurity Hardening
Segmenting and firewalling a Ceph cluster
What you'll learn
- Inventory every port a Ceph cluster uses
- Define which traffic crosses which boundary
- Write firewall rules that do not break the cluster
- Verify segmentation is actually in effect
Prerequisites
None — start here.
Verified against Ceph Tentacle 20.2.x · Ceph Squid 19.2.x (supported previous) · cephadm matches the verified Ceph release · podman 4.x · csi-rbd and csi-cephfs current · RBD / CephFS / RGW current (matches Ceph release) · Linux kernel 5.15+ (5.10 minimum) · Ubuntu 24.04 LTS (Ceph host baseline) · Debian 12 (Bookworm) (Ceph host baseline) · Rocky Linux / RHEL / AlmaLinux 9.x (Ceph host baseline) · Proxmox VE 9.x (cross-course integration) · Kubernetes 1.31+ (cross-course integration) · 2026-08-18
Why this matters in production
Firewall rules written from a partial port inventory take the cluster down in a way that looks like a Ceph fault.
The port inventory
| Service | Port | Network |
|---|---|---|
| MON msgr2 | 3300/tcp | public |
| MON msgr1 | 6789/tcp | public (legacy) |
| OSD, MGR, MDS | 6800–7300/tcp | public and cluster |
| Dashboard | 8443/tcp | management |
| mgr Prometheus exporter | 9283/tcp | management |
| node-exporter | 9100/tcp | management |
| Alertmanager | 9093/tcp | management |
| Grafana | 3000/tcp | management |
| RGW | 80 / 443/tcp, configurable | client-facing |
| cephadm SSH | 22/tcp | management |
# what is actually listening
ss -ltnp | grep -E 'ceph|radosgw|3300|6789|68[0-9][0-9]|7[0-2][0-9][0-9]'
ceph mon dump | grep -E '^[0-9]+:'
ceph orch ps --format json | python3 -c '
import sys,json
for d in json.load(sys.stdin):
p = d.get("ports")
if p: print("%-24s %-14s %s" % (d["daemon_name"], d.get("hostname"), p))'
Which traffic crosses which boundary
Clients → monitors (3300) and OSDs (6800–7300) on the public network
OSDs ↔ OSDs on the cluster network, replication and recovery
Monitors ↔ monitors on the public network, Paxos
Management → dashboard, Prometheus, SSH, from admin networks only
Nothing from outside → the cluster network, ever
| Boundary | Rule |
|---|---|
| Client subnet → public network | monitor and OSD ports, nothing else |
| Public network → cluster network | denied |
| Anywhere → cluster network | denied except cluster members |
| Admin subnet → management ports | permitted, source-restricted |
| Internet → anything | denied except RGW where intended |
Writing the rules
# nftables, on a Ceph host — public network side
nft add rule inet filter input ip saddr 10.20.0.0/16 tcp dport 3300 accept
nft add rule inet filter input ip saddr 10.20.0.0/16 tcp dport 6800-7300 accept
# cluster network side, cluster members only
nft add rule inet filter input ip saddr 10.30.0.0/24 tcp dport 6800-7300 accept
# management, from the admin subnet only
nft add rule inet filter input ip saddr 10.10.5.0/24 tcp dport { 22, 8443, 9283, 9100 } accept
The 6800–7300 range appears on both networks because OSDs bind on both.
Restricting it on one and not the other is the mistake that produces
"recovery works but clients cannot read".
# verify before committing to a default-deny
ceph -s
ceph osd tree | grep -c up
Verifying segmentation
# from a client host: monitors reachable, cluster network not
nc -z -w2 mon-01 3300 && echo "mon reachable (expected)"
nc -z -w2 10.30.0.11 6800 && echo "CLUSTER NETWORK REACHABLE — misconfigured"
# from a Ceph host, confirm both bindings
ss -ltn | awk '$4 ~ /:(3300|68[0-9][0-9])$/ {print $4}' | sort -u
# and that the cluster is using the networks intended
ceph config get global public_network
ceph config get global cluster_network
ceph osd metadata 0 --format json | python3 -c '
import sys,json
d=json.load(sys.stdin)
print("front:", d.get("front_addr"))
print("back: ", d.get("back_addr"))'
Quiz
Knowledge check · 4 questions
Q1. Why must the 6800–7300 range be permitted on both the public and cluster networks?
Q2. Firewall rules should be written from the configured `public_network` and `cluster_network` CIDRs.
Q3. Firewall a Ceph cluster without breaking it.
A cluster is being placed behind a default-deny firewall. The team has the port list from the documentation.
Q4. What should be verified from a client host after segmenting?
Passing score: 75%. Answers are checked in this browser.
Production discipline
Write firewall rules from ceph osd metadata’s bound front_addr and
back_addr, not from the configured CIDRs — the CIDRs are hints and a
daemon may bind elsewhere. Permit the 6800–7300 range on both networks;
restricting one side breaks either clients or recovery.
Cross-course references
- Kubernetes: NetworkPolicy written from intent rather than observed endpoints breaks the same way
- Linux: firewall rules must match bound addresses, not configured intent