Ingress Allowlisting Controller
The Ingress Allowlisting Controller is a Kubernetes controller designed to manage allowlisting rules for Kubernetes Ingress resources. It ensures that only trusted IPs or ranges can access specific ingress endpoints, enhancing security and compliance.
This k8s controller configures ingress allowlisting based on a custom CRD
Installation
Helm Installation (Using Local Chart)
To install the ingress-allowlisting-controller using the Helm chart provided in the repository, follow these steps:
- Clone the Repository
Clone the repository to your local machine:
git clone https://github.com/adevinta/ingress-allowlisting-controller.git
cd ingress-allowlisting-controller/helm-charts/ingress-allowlisting-controller
- Install the Chart
Install the controller using the local Helm chart. Customize the installation by specifying the namespace and configuration if needed:
helm install ingress-allowlisting-controller ./ --namespace ingress-allowlisting --create-namespace
- Verify the Installation
Ensure the controller is running in your cluster:
kubectl get pods -n ingress-allowlisting
You should see a pod named ingress-allowlisting-controller running.
Usage
Once installed, the ingress-allowlisting-controller will monitor and apply allowlisting rules to Kubernetes Ingress resources.
Example Ingress Resource
Below are examples of an Ingress resource with allowlisting annotations, using both the namespace level CIDR CRD as well
as the cluster level CRD
Namespaced version of CIDRs object
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
ipam.adevinta.com/allowlist-group: MyCidrsObject
Cluster version of the CIDRs object
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
ipam.adevinta.com/cluster-allowlist-group: MyCidrsObject
The content of the annotations can be a comma-separated list:
MyCidrsObject,MyCidrsObject2,MyCidrsObject3
Example NetworkPolicy Resource
Below are examples of NetworkPolicy resources with different policyTypes (Ingress or Egress).
NetworkPolicy with ingress with namespaced version of CIDRs object
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-allow-myips
annotations:
ipam.adevinta.com/allowlist-group: MyCidrsObject
spec:
podSelector: {} # Applies to all pods in namespace
policyTypes:
- Ingress
# Controller will populate spec.ingress[0] with ipBlock rules here
NetworkPolicy with egress using a cluster-scoped CIDRs object and predefined ports:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-allow-http
annotations:
ipam.adevinta.com/cluster-allowlist-group: http-allowed
spec:
podSelector: {} # Applies to all pods in namespace
policyTypes:
- Egress
# Controller will populate spec.egress[0] with ipBlock rules here respecting the ports
egress:
- ports:
- port: 443
- port: 80
Key Points:
- You define:
podSelector and policyTypes
- Controller manages: Automatically populates and maintains the
spec.ingress or spec.egress sections with ipBlock rules
- Overwrite behavior: Any existing
ipBlock rules in these sections will be overwritten by the controller
- Dual policyTypes: If both
Ingress and Egress are specified, the controller will populate both sections
spec:
ingress: # For Ingress policies
- from:
- ipBlock:
cidr: x.x.x.x/y
egress: # For Egress policies
- to:
- ipBlock:
cidr: x.x.x.x/y
Example CIDR and ClusterCIDR CRDs
apiVersion: ipam.adevinta.com/v1alpha1
kind: CIDRs
metadata:
name: MyCidrsObject
spec:
cidrs:
- 1.1.1.1/32
- 2.2.2.2/32
apiVersion: ipam.adevinta.com/v1alpha1
kind: ClusterCIDRs
metadata:
name: Cloudfront
spec:
cidrs:
- 120.52.22.96/27
- 205.251.249.0/24
- 180.163.57.128/26
Fetching CIDRs from remote sources
Ingress-allowlister supports synchronizing CIDRs from remote http sources.
To use this feature, configure the CIDRs or ClusterCIDRs object as follows
apiVersion: ipam.adevinta.com/v1alpha1
kind: CIDRs
metadata:
name: ec2
namespace: test
spec:
requeueAfter: 30m # Re-evaluate the remote URL every 30 minutes
location:
cel: 'data.prefixes.filter(p, p.service == "EC2" && has(p.ip_prefix)).map(p, p.ip_prefix)' # transform the AWS response into a list of strings using CEL expression
uri: https://ip-ranges.amazonaws.com/ip-ranges.json # the remote URL responding all IPs
headersFrom: # optional: inject CIDRs to the HTTP request (if the request needs to be authenticated)
secretRef: # optional: inject all keys
name: aws-authentication-headers # all aws-authentication-headers data will be used as http headers in the http request
namespace: test # optional. For CIDRs, it must match the CIDRs namespace when not empty.
configMapRef:
name: aws-headers # all aws-headers data will be used as http headers in the http request
namespace: test # optional. For CIDRs, it must match the CIDRs namespace when not empty.
---
# optional
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-headers
namespace: test
data:
My-Header: some-value
---
# optional
apiVersion: v1
kind: Secret
metadata:
name: aws-authentication-headers
namespace: test
data:
Authentication: $(echo "Bearer $token" | base64)
Fetching CIDRs from github
To fetch CIDRs stored in github repositories, you can use the github API endpoint:
apiVersion: ipam.adevinta.com/v1alpha1
kind: CIDRs
metadata:
name: my-cidrs
namespace: test
spec:
requeueAfter: 30m
location:
uri: https://api.github.com/repos/my-org/my-repo/contents/path/to/cidrs/file.json
Metrics
The operator exposes a single metric namespace_ingress_IpAllowlistingGroup_missing that, when operated appropiately, it offer several information:
# HELP namespace_ingress_IpAllowlistingGroup_missing Number of missing IpAllowlistingGroup objects. >0 implies expected objects were not found
# TYPE namespace_ingress_IpAllowlistingGroup_missing gauge
namespace_ingress_IpAllowlistingGroup_missing{cidrs_name="alvarocidr",ingress="kube-nurse-kubenurse",namespace="cre-system"} 0
When the metric exists and equals 0, it means that there are no errors; the given object in the given namespace associated to the given ingress exists and has been resolved adequately.
When the metric exists and equals 1 means that there was an error resolving the cidr_name, probably, because the object didn't exist in the namespace.
Common operations:
number of ingresses with allowlistGroup annotations:
count(sum(namespace_ingress_IpAllowlistingGroup_missing) by (ingress))
number of ingresses with failed annotations:
count(sum(namespace_ingress_IpAllowlistingGroup_missing) by (ingress) > 0)