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:

  • id and name are for identification.
  • description explains the policy.
  • The rego block contains the logic.
    • The first deny rule checks if container.securityContext.runAsUser is explicitly set to 0. If it is, it triggers a violation.
    • The second deny rule is a bit more nuanced. It catches containers where runAsUser isn’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.

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.

Want structured learning?

Take the full Aqua course →