Cloudflare Ingress Guard is a small Kubernetes controller that protects your ingresses by automatically restricting access to Cloudflare's official IP ranges. The result is that it is no longer possible to bypass Cloudflare's "orange cloud" proxy service for DNS records pointing to those ingresses.
It supports two modes, which can be used simultaneously:
- NGINX Ingress Controller — via
networking.k8s.io/v1 Ingressresources - NGINX Gateway Fabric — via the
CloudflareWhitelistFilterCRD
Cloudflare IP ranges are fetched directly from Cloudflare (https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6) and cached for 7 days.
IMPORTANT: make sure your nginx ingress controller is configured with service.externalTrafficPolicy: Local. Otherwise, the filter will see an internal cluster IP instead of the connecting Cloudflare IP.
The controller watches all Ingress resources in the cluster.
If an ingress has the annotation:
cloudflare-ingress-guard.inias.eu/enabled: "true"it ensures the annotation
nginx.ingress.kubernetes.io/whitelist-source-range: "<Cloudflare IPs>"is set with the current Cloudflare IPv4 and IPv6 ranges.
If the whitelist-source-range annotation is already present, existing IP ranges it may contain are not preserved.
When the cloudflare-ingress-guard.inias.eu/enabled annotation is removed, the NGINX whitelist annotation will not be removed or cleaned up. Cleanup should be done manually; you may want to specify another type of protection or a different whitelist.
Alternatively, explicitly setting cloudflare-ingress-guard.inias.eu/enabled: false will drop any existing whitelist. This makes it easier to toggle the functionality for testing or debugging.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cloudflare-ingress-guard.inias.eu/enabled: "true"
spec:
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80The controller needs RBAC permissions to modify the relevant ingresses. The following rule suffices:
- apiGroups: [networking.k8s.io]
resources: [ingresses]
verbs: [get, list, watch, update, patch]The controller provides a CloudflareWhitelistFilter CRD for use with NGINX Gateway Fabric.
When you create a CloudflareWhitelistFilter in a namespace, the operator creates and maintains a
SnippetsFilter
(gateway.nginx.org/v1alpha1) in the same namespace, populated with NGINX allow directives for all
current Cloudflare IP ranges followed by deny all.
You then reference the produced SnippetsFilter from your HTTPRoute rules via an ExtensionRef filter.
Note: The Kubernetes Gateway API has no standard, implementation-agnostic mechanism for IP allowlisting. This feature therefore requires NGINX Gateway Fabric v1.4+ with the
SnippetsFilterfeature gate enabled.
IMPORTANT: The NGF Gateway service must be configured with externalTrafficPolicy: Local. Otherwise, the filter will see an internal cluster IP instead of the connecting Cloudflare IP.
- Create a
CloudflareWhitelistFilterin each namespace where you haveHTTPRouteresources to protect. - The operator creates a
SnippetsFilterwith the same name (or the name specified inspec.snippetsFilterName) in the same namespace. - Reference the
SnippetsFilterfrom yourHTTPRouterules via anExtensionReffilter.
# One CloudflareWhitelistFilter per namespace:
apiVersion: cloudflareingressguard.inias.eu/v1alpha1
kind: CloudflareWhitelistFilter
metadata:
name: cloudflare-ips
namespace: my-app
# spec.snippetsFilterName is optional; defaults to metadata.name# Reference the produced SnippetsFilter from your HTTPRoute:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: my-app
spec:
parentRefs:
- name: my-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: ExtensionRef
extensionRef:
group: gateway.nginx.org
kind: SnippetsFilter
name: cloudflare-ips # same as CloudflareWhitelistFilter name
backendRefs:
- name: my-app
port: 80After reconciliation, the operator creates a SnippetsFilter like:
apiVersion: gateway.nginx.org/v1alpha1
kind: SnippetsFilter
metadata:
name: cloudflare-ips
namespace: my-app
ownerReferences:
- apiVersion: cloudflareingressguard.inias.eu/v1alpha1
kind: CloudflareWhitelistFilter
name: cloudflare-ips
controller: true
blockOwnerDeletion: true
spec:
snippets:
- context: http.server.location
value: |
allow 173.245.48.0/20;
allow 103.21.244.0/22;
# ... all Cloudflare CIDRs ...
deny all;If you want the produced SnippetsFilter to have a different name than the CloudflareWhitelistFilter,
use spec.snippetsFilterName:
apiVersion: cloudflareingressguard.inias.eu/v1alpha1
kind: CloudflareWhitelistFilter
metadata:
name: my-filter
namespace: my-app
spec:
snippetsFilterName: cloudflare-allowlistThis creates a SnippetsFilter named cloudflare-allowlist instead of my-filter.
The SnippetsFilter feature must be explicitly enabled in your NGF installation. Add the following
to your NGF Helm values:
nginx-gateway:
config:
featureGates:
SnippetsFilters: trueOr if installing via manifests, set the --feature-gates=SnippetsFilters=true flag on the
nginx-gateway container.
The controller needs the following RBAC permissions for Gateway API mode:
- apiGroups: ["cloudflareingressguard.inias.eu"]
resources: ["cloudflarewhitelistfilters"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["cloudflareingressguard.inias.eu"]
resources: ["cloudflarewhitelistfilters/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["gateway.nginx.org"]
resources: ["snippetsfilters"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]Since SnippetsFilter is namespace-scoped and HTTPRoute extensionRef filters are local references
(no cross-namespace support), you need one CloudflareWhitelistFilter per namespace where you have
HTTPRoute resources to protect.
When a CloudflareWhitelistFilter is deleted, the SnippetsFilter is garbage-collected automatically.
However, any HTTPRoute rules that reference the deleted SnippetsFilter will report a ResolvedRefs
error from NGF until the ExtensionRef filter is removed from the HTTPRoute manifest.