A Kubernetes operator that turns remote IP/CIDR allowlists (CDN edge ranges, cloud
provider IP lists, partner allowlists, etc.) into NetworkPolicy resources — kept
automatically in sync on a schedule.
Define one IPPoolPolicy custom resource pointing at one or more text URLs (and/or
inline CIDRs), tell it which namespaces/pods to target, and the operator periodically
fetches the lists, normalizes the entries, and creates/updates a NetworkPolicy in
every matching namespace.
Allowing traffic only from a CDN's or cloud provider's published edge IPs is a common
requirement, but those IP ranges change over time. Hand-maintaining NetworkPolicy
CIDR blocks (and remembering to update them) doesn't scale. ip-pool-operator automates
that loop:
[ IP/CIDR list URL(s) ] --(fetch on interval)--> [ IPPoolPolicy ] --(reconcile)--> [ NetworkPolicy per namespace ]
- Multiple sources per policy — combine any number of URLs and inline CIDRs; results
are merged, deduplicated, and normalized (bare IPs become
/32or/128). - Comment support — source lists may use
#comments (full-line or inline). - Flexible targeting — select namespaces with a
namespaceSelectorand, within them, apodSelector(defaults to all pods). - Ingress, Egress, or both — and any set of ports/protocols.
- Periodic refresh — configurable
refreshInterval(default1h) re-fetches the source lists and updates policies only when something actually changed. - Clean teardown — deleting an
IPPoolPolicyremoves everyNetworkPolicyit owns via a finalizer. - Status reporting —
kubectl get ipppshows direction, ports, selectors, and last sync time at a glance.
- You create an
IPPoolPolicyresource (cluster-scoped). - The controller fetches every URL in
spec.source.urls, merges inspec.source.inlineCIDRs, strips comments/blank lines, and normalizes each entry to a CIDR. - It lists namespaces matching
spec.target.namespaceSelector. - For each matching namespace, it creates/updates a
NetworkPolicynamed<namePrefix><policy-name>(default prefixippool-) that allows the configured ports to/from the resolved CIDRs, scoped byspec.target.podSelector. - It requeues itself after
spec.refreshIntervalto repeat the cycle.
A content hash is stored on the NetworkPolicy so unchanged source lists don't cause
needless updates.
The latest install manifest (CRDs + RBAC + controller Deployment) is published as a release asset. Pick a tag from the Releases page and apply it:
kubectl apply -f https://github.com/SepehrImanian/ip-pool-operator/releases/latest/download/install.yamlThis creates a ip-pool-operator-system namespace and runs the controller there.
apiVersion: net.techonfire.ir/v1alpha1
kind: IPPoolPolicy
metadata:
name: cdn-edge-ingress
spec:
refreshInterval: 30m
source:
urls:
- https://www.arvancloud.ir/en/ips.txt
target:
namespaceSelector:
matchLabels:
enforce-ip-allowlist: "true"
podSelector:
matchLabels:
app: nginx
policy:
types: [Ingress]
namePrefix: ippool-
ports:
- port: 443
protocol: TCP
- port: 80
protocol: TCPkubectl apply -f cdn-edge-ingress.yaml
kubectl label namespace web enforce-ip-allowlist=trueMore worked examples are in config/samples/, including:
- Arvan Cloud edge IPs (ingress)
- Sotoon edge IPs (ingress and egress)
- Cloudflare edge IPs (ingress)
- bunny.net edge server IPs (ingress, IPv4 + IPv6)
- Mixing a remote list with your own inline CIDRs (e.g. office IPs, partner ranges)
Any provider that publishes its IP ranges as a plain-text, one-CIDR-per-line URL works
the same way — just point spec.source.urls at it. Providers that only publish ranges
as JSON (e.g. AWS, Fastly, Akamai) aren't supported directly today; you'd need to mirror
their list as plain text somewhere the operator can fetch it.
kubectl get ippoolpolicies
# NAME DIR PORTS POD-SELECTOR NS-SELECTOR
# cdn-edge-ingress Ingress TCP:80,TCP:443 app=nginx enforce-ip-allowlist=truekubectl delete ippoolpolicy --all
kubectl delete -f https://github.com/SepehrImanian/ip-pool-operator/releases/latest/download/install.yamlDeleting an IPPoolPolicy first removes the NetworkPolicy objects it created in every
target namespace, via a finalizer.
Note: A
NetworkPolicyis only enforced if your cluster's CNI supportsNetworkPolicy(e.g. Calico, Cilium). Clusters without a network-policy-aware CNI will accept these resources but won't enforce them.
| Field | Type | Description |
|---|---|---|
urls |
[]string |
One or more URLs serving plain text, one IP/CIDR per line. # starts a comment (full-line or inline). |
inlineCIDRs |
[]string |
Additional IPs/CIDRs to merge in directly. |
httpHeaders |
map[string]string |
Headers sent with each URL request (e.g. Authorization). |
If both urls and inlineCIDRs are set, their results are unioned and deduplicated.
| Field | Type | Description |
|---|---|---|
namespaceSelector |
LabelSelector |
Namespaces to apply the policy in. If omitted, no namespaces match — be explicit. |
podSelector |
LabelSelector |
Pods within each matched namespace the policy applies to. Empty/omitted = all pods. |
| Field | Type | Description |
|---|---|---|
types |
[]string |
One or both of Ingress, Egress. Required. |
ports |
[]NetworkPolicyPort |
Ports/protocols applied to the generated rule(s). |
namePrefix |
string |
Prefix for generated NetworkPolicy names. Default ippool-. |
metav1.Duration (e.g. "30m", "1h"). How often to re-fetch sources and reconcile.
Default 1h.
observedGeneration, lastSyncTime, effectiveCIDRs, namespaces, policies, hash,
plus the human-readable dirString/portList/podSelectorString/nsSelectorString
columns shown by kubectl get.
The full schema is in
config/crd/bases/net.techonfire.ir_ippoolpolicies.yaml.
The controller needs to read namespaces and manage IPPoolPolicy and NetworkPolicy
resources cluster-wide; see config/rbac/role.yaml for the
exact rules. All of this is included in install.yaml.
Tagged releases (vX.Y.Z) publish two artifacts:
- A multi-arch (
linux/amd64,linux/arm64) controller image toghcr.io/<owner>/ip-pool-operator:<tag>. - A consolidated
install.yaml(CRDs + RBAC + Deployment) attached to the GitHub Release, for one-command installs.
See the Releases page for
available versions. :latest tracks the most recent build of main.
- Go v1.22+
- Docker (or another OCI tool) v17.03+
- kubectl v1.11.3+
- A Kubernetes v1.11.3+ cluster (e.g. kind — see
kind.yaml)
make install # apply CRDs
make run # run the controller out-of-cluster, using your current kubeconfigmake test # unit/envtest suite
make test-e2e # e2e suite (spins up a kind cluster)make docker-build docker-push IMG=<registry>/ip-pool-operator:tag
make deploy IMG=<registry>/ip-pool-operator:tagmake build-installer IMG=<registry>/ip-pool-operator:tag
# -> dist/install.yamlRun make help for the full list of targets. This project is scaffolded with
Kubebuilder; its conventions (config/, Makefile
targets, etc.) follow the standard Kubebuilder layout.
Contributions are welcome — see CONTRIBUTING.md for how to set up your environment, run tests, and submit a pull request.
Licensed under the Apache License, Version 2.0.