Description
This rule checks that the Kubernetes service network CIDR (the service_subnet used to allocate ClusterIP virtual addresses) is configured within private, non-routable RFC 1918 address space rather than a publicly routable range. Service IPs are purely internal to the cluster.
On DigitalOcean, VPC-native clusters take a separate service_subnet in CIDR notation that must follow RFC 1918 and not overlap with the cluster subnet, other VPCs, or other VPC-native clusters. DigitalOcean recommends a /19 for services to support many virtual IPs.
Clusters whose service_subnet is outside RFC 1918 private ranges.
Service subnets that overlap the cluster (pod) subnet or a peered VPC.
Service ranges too small to cover the expected number of services.
Rationale
Service (ClusterIP) addresses are used only for internal, in-cluster routing and are never meant to be reachable from outside. Allocating them from public IP space creates ambiguity with legitimate external addresses and raises the chance that misrouted traffic reaches, or is confused for, real internet destinations. Keeping the service CIDR in RFC 1918 space removes that ambiguity.
Non-overlapping private ranges are also required for the cluster to route correctly alongside peered VPCs, so a private, well-planned service subnet is both a security and a correctness requirement.
ClusterIP addresses are internal-only; private space matches their purpose.
Avoids collisions with pod subnet and peered VPC ranges.
Impact
Pros:
Service virtual IPs stay clearly internal and non-routable.
Clean interoperation with VPC peering and the pod subnet.
Reduces exposure risk from misrouted internal traffic.
Cons:
The service subnet is fixed at creation and generally immutable, so a bad range means rebuilding.
Requires planning a non-overlapping range sized for expected service count (a /19 is recommended).
Requires Kubernetes 1.31+ VPC-native networking (doctl 1.115.0+).
Default Value
New VPC-native clusters (Kubernetes 1.31+, default since 12 December 2024) require an RFC 1918 service subnet; the specific default CIDR is not documented, so verify the value in your account.
Pre-Requisite
Control Panel access to view the cluster's networking, or an API token / authenticated doctl (v1.115.0+).
An IP address plan of your RFC 1918 ranges to avoid overlaps.
For remediation: ability to create a replacement cluster, since the service subnet is immutable after creation.
Remediation
Test Plan
Using the DigitalOcean Control Panel:
Sign in to cloud.digitalocean.com and open Kubernetes.
Select the cluster and view its service subnet in the cluster details, or inspect it via the API below.
Confirm the service CIDR sits within 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16.
Pass: the service_subnet is a private RFC 1918 range.
Fail: the service_subnet uses a public (non-RFC 1918) range.
Using doctl or the API:
Read the service_subnet and confirm it is within RFC 1918 space.
doctl kubernetes cluster get <cluster-id> --format Name,ServiceSubnet
curl -s -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/kubernetes/clusters/$CLUSTER_ID" | jq '.kubernetes_cluster.service_subnet'
Pass if service_subnet falls within 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16. Fail if it falls outside those RFC 1918 ranges.
Implementation Steps
Using the DigitalOcean Control Panel:
The service subnet is fixed at creation and cannot be edited on a running cluster.
To remediate, create a new cluster and specify a private service subnet, then migrate workloads.
In the Create Kubernetes Cluster flow (or via doctl), set the VPC-native service subnet to an RFC 1918 range such as 172.16.0.0/19.
Using doctl or the API:
Service subnet is immutable, so remediation is create-new-then-migrate; set the private subnet at creation.
doctl kubernetes cluster create <new-name> --region nyc1 --version 1.31 --node-pool "name=pool1;size=s-2vcpu-2gb;count=3" --cluster-subnet "192.168.0.0/16" --service-subnet "172.16.0.0/19"
curl -s -X POST -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H 'Content-Type: application/json' -d '{"name":"<new-name>","region":"nyc1","version":"1.31","cluster_subnet":"192.168.0.0/16","service_subnet":"172.16.0.0/19","node_pools":[{"size":"s-2vcpu-2gb","count":3,"name":"pool1"}]}' "https://api.digitalocean.com/v2/kubernetes/clusters"
Backout Plan:
Keep the original cluster running until the replacement is validated.
If the new service range causes issues, route traffic back to the original cluster.
Delete the replacement only after confirming the original serves correctly.