Aqua Security’s compliance checks are incredibly flexible, letting you go beyond built-in CIS, PCI-DSS, or NIST benchmarks to enforce your own specific security policies.
Let’s say you want to ensure no container in your cluster is running as the root user. This is a common security best practice. We can build a custom check for this.
First, we need to define the check itself. This is done using a YAML file that specifies the id, name, description, and the actual rego (Rego Language, the language of Open Policy Agent) code that will perform the check.
apiVersion: secrets.infosec.aqua.io/v1alpha1
kind: ComplianceCheck
metadata:
name: no-root-user
namespace: aqua-security # Or your chosen namespace
spec:
id: "aqua-custom-no-root-user"
name: "Container should not run as root user"
description: "Ensures that containers are configured to run with a non-root user to reduce the attack surface."
rego: |
package main
deny[msg] {
container := input.container
container.securityContext.runAsUser == 0 # 0 is the UID for root
msg := sprintf("Container '%v' is running as root user (UID 0).", [container.name])
}
# This part handles pods that might not explicitly set runAsUser but default to root
deny[msg] {
container := input.container
not is_defined(container.securityContext.runAsUser)
# This is a simplification; in a real scenario, you'd need to check image defaults or Kubernetes defaults.
# For this example, we'll assume if not explicitly set, it's a potential risk we want to flag.
# A more robust check would involve inspecting the image's user or K8s pod security policies.
msg := sprintf("Container '%v' does not explicitly specify a non-root user. Running as root is a risk.", [container.name])
}
In this ComplianceCheck resource:
idandnameare for identification.descriptionexplains the policy.- The
regoblock contains the logic.- The first
denyrule checks ifcontainer.securityContext.runAsUseris explicitly set to0. If it is, it triggers a violation. - The second
denyrule is a bit more nuanced. It catches containers whererunAsUserisn’t explicitly defined in the pod spec. While Kubernetes might have defaults or images might specify a user, this rule flags it as a potential risk for manual review or if your cluster’s security policies require explicit non-root user definition.
- The first
To use this, you’d apply it to your Kubernetes cluster:
kubectl apply -f no-root-user-check.yaml
Once applied, Aqua Security’s scanner will pick up this custom check and incorporate it into its compliance reports. When it scans your running containers or container images, it will evaluate them against this no-root-user policy.
Let’s see what a violation looks like in Aqua’s UI. Imagine you have a deployment running a container like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vulnerable-app
spec:
replicas: 1
selector:
matchLabels:
app: vulnerable
template:
metadata:
labels:
app: vulnerable
spec:
containers:
- name: main-app
image: nginx:latest # Nginx typically runs as root by default
ports:
- containerPort: 80
securityContext:
runAsUser: 0 # Explicitly running as root
When Aqua scans this, the no-root-user check will trigger. In the Aqua Security console, under Compliance, you’d see a violation for the aqua-custom-no-root-user check, detailing that the main-app container in the vulnerable-app deployment is running as root.
To fix this, you’d modify your deployment’s securityContext:
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
replicas: 1
selector:
matchLabels:
app: secure
template:
metadata:
labels:
app: secure
spec:
containers:
- name: main-app
image: nginx:latest
ports:
- containerPort: 80
securityContext:
runAsUser: 101 # A common non-root UID for nginx
# Or more robustly:
# runAsNonRoot: true
The fix involves setting runAsUser to a non-zero UID (like 101 for Nginx, which is often configured to run as user nginx with UID 101) or, even better, setting runAsNonRoot: true. The latter is more declarative and relies on the container image being built correctly to not run as root. The rego logic container.securityContext.runAsUser == 0 directly corresponds to the runAsUser: 0 configuration, and the not is_defined(container.securityContext.runAsUser) part would catch cases where runAsUser isn’t set at all, prompting you to define it or ensure runAsNonRoot: true is used.
This mechanism allows you to codify virtually any security policy. You could write checks for specific disallowed binaries, required environment variables, specific network policies, or even enforce the use of particular base images. The power lies in translating your organization’s security posture into Rego code that Aqua can then enforce across your environment.
The next step is often to automate the enforcement of these checks, not just report on them.