How do you limit a Kubernetes LoadBalancer Service to specific client IP ranges, and what must hold for that limit to work?
answer
- a Service spec list of CIDRs
- two enforcers, both conditional
- VIP-like balancer keeps client address
- nodePort is not covered
- empty list adds nothing
basics
~20 sList the allowed CIDRs in the Service's spec.loadBalancerSourceRanges. The load balancer's own firewall enforces them where the provider supports it, and kube-proxy adds a filter. Both only work if the real client address arrives, and neither protects the NodePort.
solid answer
~40 sSet `spec.loadBalancerSourceRanges` on the `type: LoadBalancer` Service to a list of CIDRs. The load-balancer implementation turns that list into firewall or security rules where it supports the field, and it ignores the field where it does not. kube-proxy also installs a source filter for packets addressed to the load-balancer IP. That filter only helps with pass-through, VIP-like load balancers that keep the client's source address. A load balancer that proxies the connection, or forwards to the node's `nodePort`, shows the node its own address instead. Two gaps remain. The Service's `nodePort` is **not** subject to the list, so anyone who can reach a node IP can bypass it. And an empty list means no restriction from Kubernetes.
code
yaml · 17 linesapiVersion: v1
kind: Service
metadata:
name: telemetry-gateway
namespace: ingest
spec:
type: LoadBalancer
selector:
app: telemetry-gateway
ports:
- name: mqtts
port: 8883
targetPort: 8883
protocol: TCP
loadBalancerSourceRanges:
- 203.0.113.64/26
- 198.51.100.128/27go deeper
Name the field, spec.loadBalancerSourceRanges, and say it takes CIDR blocks. Then say that the Service's nodePort is not covered by it.
Explain the two enforcers, the provider's firewall and kube-proxy's filter, and why kube-proxy's only helps when the load balancer keeps the client address.
Show you would test the bypass paths yourself: node IPs on the nodePort, provider health probes outside the ranges, and carrier NAT changing the address you have to allow.
Frame the allowlist as one layer of exposure control, weighed against node-level firewalls, disabling NodePort allocation and device authentication. Decide who owns each layer across teams.
## What the field is A Kubernetes **Service** of `type: LoadBalancer` asks the cluster's load-balancer implementation for an external address. Usually that implementation is the cloud-controller-manager's service controller, or a bare-metal equivalent. The field `spec.loadBalancerSourceRanges` is a list of CIDR blocks. It says: *only clients whose source address falls inside one of these ranges may use this load balancer.* In our example, **an IoT telemetry ingest gateway** should accept connections only from two carrier device gateways, `203.0.113.64/26` and `198.51.100.128/27`. It runs on **a 3-control-plane, 27-worker self-managed cluster**. ## Who enforces it Two layers can act on the list, and they behave differently: | Layer | What it does | When it helps | |---|---|---| | The load-balancer implementation | Programs its own firewall or security rules from the list | Only if the implementation supports the field; the API says it is **ignored** otherwise | | **kube-proxy** on each node | Adds a filter so that packets addressed to the load-balancer IP are accepted only from listed sources | Only for **VIP-like** load balancers that deliver packets still addressed to the load-balancer IP, with the client's address intact | In iptables mode, kube-proxy's filter lives in the `nat` table and is checked **before** traffic is marked for masquerade. So it inspects the original client address under either `externalTrafficPolicy` value. ## What must hold for the limit to work 1. **The client's real address must reach whoever enforces the list.** A load balancer that ends the client's connection and opens a new one to the node presents *its own* address. kube-proxy then compares the load balancer's IP, not the device's, against the list. 2. **The implementation must support the field**, or kube-proxy's filter must apply (a VIP-like load balancer). If neither is true, the field silently does nothing. 3. **The ranges must include the addresses you actually see.** Devices behind carrier NAT show the carrier's public address, not the device's own address. 4. **Health checks must still get through.** A provider whose health probes come from addresses outside the list may mark every node unhealthy. How providers handle this varies, so check the provider's documentation. ## The gaps it leaves open - **The `nodePort` is not covered.** A `LoadBalancer` Service normally also allocates a `nodePort`, and kube-proxy does not apply `loadBalancerSourceRanges` to it. Anyone who can reach a node IP on that port reaches the gateway. Close it with network firewalls or security groups on the nodes, or set `allocateLoadBalancerNodePorts: false` where your implementation routes straight to pods. - **In-cluster clients** reach the Service through its ClusterIP, which the list does not govern. Use a **NetworkPolicy** for pod-to-pod restrictions. - **An empty list** adds no restriction from Kubernetes. What remains open depends on the provider's defaults. - **It is not authentication.** A source-address allowlist narrows exposure, but the gateway should still authenticate devices, for example with mutual TLS. ## Relationship to client-IP visibility The filter and the application see different things. Under `externalTrafficPolicy: Cluster`, kube-proxy may filter correctly on the client address and then masquerade the packet, so the gateway's logs show a **node IP**. If the application itself must see the device address, for rate limiting or audit, that is a separate requirement. `externalTrafficPolicy: Local` meets it because it skips the masquerade. ## Checking it - `kubectl get svc telemetry-gateway -o jsonpath='{.spec.loadBalancerSourceRanges}'` shows the list the API stored. - Test from an address outside the ranges: the connection should fail at the load balancer or be dropped at the node. - Test the node IP on the `nodePort` from the same outside address. If it succeeds, the bypass is open. ## Common mistakes - **Assuming the list is enforced everywhere.** Some implementations ignore it silently. Always test from an outside address rather than trusting the stored field. - **Allowing device addresses instead of the addresses the load balancer sees.** Devices behind carrier NAT show the carrier's public addresses, so an allowlist of device subnets blocks everyone. - **Forgetting the nodes.** Node IPs that are reachable from the internet on the `nodePort` bypass the whole allowlist. Pair the field with node-level firewall rules. - **Treating it as a policy for pods.** Pod-to-pod restrictions belong in a NetworkPolicy, not in a load-balancer field.
- Why can a client outside the allowed ranges still reach the gateway after you set loadBalancerSourceRanges?Most likely through the `nodePort`. A `LoadBalancer` Service normally allocates one, and kube-proxy does not apply `loadBalancerSourceRanges` to NodePort traffic, so any host that can reach a node IP on that port gets in. The other common cause is a provider that does not support the field and silently ignores it. Close node ports with network-level firewalls, or disable NodePort allocation where the implementation routes directly to pods.
- Does loadBalancerSourceRanges govern traffic from pods inside the cluster?No. Pods normally reach the Service through its ClusterIP, and the list applies to the load-balancer address. Even a pod sending to the load-balancer IP from inside the cluster is handled by kube-proxy with Cluster semantics rather than through the external load balancer. To restrict pod-to-pod traffic, use a NetworkPolicy, which is a separate mechanism enforced by the network plugin.
saying these in an interview costs you the question
- loadBalancerSourceRanges only works with externalTrafficPolicy: Local
- Setting the ranges also locks down the Service's nodePort
- Every provider enforces the field, so it can never be silently ignored
- A source-IP allowlist replaces authenticating the devices
- Pod-to-pod traffic through the ClusterIP is filtered by the same list