Having recently completed a comprehensive evaluation of container security tooling for a multi-cloud deployment, I found the process of configuring Aqua Security for Helm chart scanning to be non-trivial, albeit highly effective once properly tuned. The documentation, while thorough, often assumes a level of operational familiarity with Aqua's control plane that can lead to significant gaps in a production pipeline. This post details a replicable, step-by-step configuration, including the necessary CLI commands, YAML structures, and indexing strategies for the resulting vulnerability data.
The primary objective is to integrate Helm chart scanning into a CI/CD pipeline, treating the charts as static code to be analyzed *before* any `helm install` is executed on a Kubernetes cluster. This requires the Aqua CLI (`scanner`) and a correctly configured `scanner-cli` user with permissions to access the Aqua server.
**Step 1: Environment and Authentication Configuration**
First, ensure the Aqua CLI is installed and authenticated. The credentials are typically service account keys for automation.
```bash
# Download and configure the scanner CLI
curl -s https://get.aquasec.com/cli | sh
sudo mv scanner /usr/local/bin/
# Set the Aqua server endpoint and authenticate
export AQUA_URL= https://your-aqua-server.com
export AQUA_USERNAME=scanner-cli
export AQUA_PASSWORD=your-service-account-password
# Alternatively, using a token (preferred for CI)
export AQUA_TOKEN=your-generated-token
scanner login --url $AQUA_URL --user $AQUA_USERNAME --password $AQUA_PASSWORD
```
**Step 2: Structuring the Scan Command for Helm Charts**
Aqua does not natively understand Helm templating. Therefore, you must render the Helm charts into raw Kubernetes manifests before scanning. This is a critical step, as scanning the `templates/` directory directly will yield invalid YAML and fail.
```bash
# Render the Helm chart to a temporary directory
helm template ./my-chart --output-dir ./rendered-manifests
# Recursively scan the rendered manifests directory
scanner analyze --host $AQUA_URL --user $AQUA_USERNAME
--local ./rendered-manifests
--registry "Helm Registry" --check-only
--html --output ./aqua-scan-report.html
```
**Step 3: Integrating with Policy Enforcement**
The raw scan results are verbose. To enforce policy, you must define and apply a set of security controls in the Aqua console under "Policies -> Image Assurance." Key policies for Helm charts include:
* **CIS Benchmark Checks:** Ensure manifests comply with Kubernetes CIS benchmarks.
* **Sensitive Data Exposure:** Scan for hardcoded secrets in environment variables or ConfigMap/Secret definitions.
* **Privilege Misconfigurations:** Flag containers with `privileged: true`, `hostPID`, or `hostNetwork`.
* **Resource Limitations:** Enforce the presence of memory and CPU limits/requests.
The CLI command can then be configured to fail the build based on policy violations:
```bash
scanner analyze --host $AQUA_URL --user $AQUA_USERNAME
--local ./rendered-manifests
--registry "Helm Registry"
--fail-on-policy
```
**Step 4: Data Persistence and Indexing for Trend Analysis**
The scan outputs (JSON format) should be stored in a time-series database for longitudinal analysis. I recommend parsing the JSON and indexing key fields for efficient querying. Below is a simplified schema for a PostgreSQL table to store findings:
```sql
CREATE TABLE aqua_helm_findings (
scan_id UUID PRIMARY KEY,
chart_name VARCHAR(255),
chart_version VARCHAR(50),
scan_timestamp TIMESTAMPTZ DEFAULT NOW(),
total_critical INT,
total_high INT,
resource_kind VARCHAR(50),
resource_name VARCHAR(255),
namespace VARCHAR(100),
misconfiguration_type VARCHAR(100),
cis_check_id VARCHAR(20),
raw_finding JSONB
);
CREATE INDEX idx_scan_timestamp ON aqua_helm_findings(scan_timestamp);
CREATE INDEX idx_chart_name_version ON aqua_helm_findings(chart_name, chart_version);
CREATE INDEX idx_misconfiguration_type ON aqua_helm_findings(misconfiguration_type);
CREATE INDEX idx_cis_check ON aqua_helm_findings(cis_check_id);
CREATE INDEX idx_raw_finding_gin ON aqua_helm_findings USING GIN(raw_finding);
```
The `JSONB` column with a GIN index allows for efficient ad-hoc queries into the raw scan payload, while the structured columns enable fast aggregations for dashboards (e.g., "Top 5 charts by critical misconfigurations over the last 30 days").
**Pitfalls and Optimizations:**
* **Docker-in-Docker (DinD) in CI:** The scanner requires a Docker daemon. In GitLab CI or Jenkins, you must run the job in a DinD sidecar container with proper volume mounting for the rendered manifests.
* **False Positives with `helm template`:** Some Helm chart logic (like `if` conditions) may generate empty manifests or placeholders. Consider post-processing the `rendered-manifests` directory to remove zero-byte YAML files before scanning.
* **Performance:** Scanning hundreds of rendered manifests is I/O and network intensive. Implement a caching layer for the scanner's vulnerability database updates, and consider a differential scan approach if the chart change rate is high.
The primary benefit of this method is the shift-left of security policy, catching misconfigurations at the artifact generation stage rather than during runtime admission control. The data model outlined also allows for correlation between Helm chart misconfigurations and runtime incidents, providing a quantitative measure of risk reduction.
Great detail! I'm actually evaluating Aqua for a similar pipeline right now. When you mention the service account keys for the CLI authentication, is that a distinct set of credentials from the ones used for the main console login? I'm trying to figure out if I need to get our IT ops team to create a new service account, or if I can reuse an existing one.
Thanks for writing this up! That curl command is super helpful. Quick question about the first step: do we need to install the scanner CLI on the machine running the pipeline, or would it be better to run it inside a dedicated scanning container image?
Yeah, they're distinct. The console login is typically a user session with a password or SSO. The CLI needs a service account with API permissions, usually generated from the Aqua console (Administration -> Access Management -> Service Accounts). You can't reuse your personal login credentials for automation.
It's generally a good idea to create a dedicated service account for this pipeline task anyway, scoped to just the permissions needed for scanning and uploading results. That way you can rotate the keys independently if needed.
Dedicated container. Always. The number of times I've seen a pipeline fail because someone updated a shared runner and broke the CLI dependencies... painful.
Your scanning logic becomes a single, versioned artifact. If Aqua pushes a breaking CLI change, you control the rollout in your own image instead of scrambling during a deploy.
CRM is a necessary evil
Totally feel you on this. The container approach saved us last quarter when the CLI changed its default output format from JSON to YAML. Our shared runner would've choked, but our pinned image just kept humming along.
One thing I'd add - make sure your Dockerfile uses a specific version tag, not 'latest'. We also hash-pin the download URL for the CLI binary inside the image. It's a bit more upfront work, but it locks everything down.
Thanks for sharing this detailed process. The part about treating Helm charts as static code *before* install really clicks with how we work. I'm new to Aqua and was wondering about a specific part of your first step. You mention the service account keys, but how do you handle the initial secret injection into the pipeline? Is it through a mounted volume in the container, or are you using a secrets manager integration to fetch it at runtime? I'd be worried about the key being exposed in the build logs if I'm not careful.
Exactly right on the dedicated service account. We tie ours to a specific "CI Scanner" role we defined within Aqua, with permissions limited to running scans and posting results. This is a key contract negotiation point with Aqua's sales team - making sure their role-based access control is granular enough for your CI needs.
For rotating the keys, we schedule it quarterly. It's a simple process of generating a new key in the console and updating the secret in our vault, but having it scoped to just that pipeline means we don't disrupt any human logins or other integrations.
Good start, but you're missing the actual auth command. That curl just gets the CLI. You need to run `scanner login` with the service account keys from your vault, and I hope you're not storing those creds in plain text.
The pre-install scanning is the right approach. It catches configuration drift in the chart templates before they become live resources. Your CI step should fail the build if the scan finds any critical misconfigurations from your defined policies.
Five nines? Prove it.