Compliance as Code with Witness and Hadolint
Hadolint and Witness for NIST SP 800-53 AC-6, CM-2, and CM-7 Controls
Many of us are familiar with the concept of policy as code. Tools like OPA Gatekeeper, Kyverno, and HashiCorp Sentinel let us define and enforce policies programmatically. They promise automation, consistency, and direct integration into development pipelines, effectively shifting security left. When I worked on DoD Platform One, we used Gatekeeper to implement many NIST SP 800-53 AC - Access Controls on our infrastructure. But we faced a challenge: we couldn’t effectively create and enforce policies over the entire process.
This article kicks off a series titled "Compliance as Code," where I’ll explore how to shift compliance left using tools like Hadolint and Witness. This first entry will focus on mapping Hadolint to NIST SP 800-53 controls and enforcing those rules with Witness policies. We’ll also dive into how NIST SP 800-204D provides technical guidance for integrating these tools into secure and compliant software pipelines.
Why Shift Compliance Left?
Traditional compliance methods rely on manual checklists and periodic audits, creating bottlenecks that slow the software release process. These processes also increase the risk of human error and make it difficult to adapt to fast-paced environments. By shifting compliance left—embedding it directly into development pipelines—we can:
Automate Compliance: Reduce manual intervention by enforcing policies programmatically.
Enhance Consistency: Ensure uniform application of policies across all projects and teams.
Accelerate Delivery: Integrate compliance into existing workflows to minimize delays.
Increase Transparency: Generate auditable evidence of compliance in real-time.
NIST SP 800-204D provides guidance for applying these principles to secure pipelines. It outlines best practices for building, verifying, and deploying software securely using automation tools. Witness, for example, embodies the principles of NIST SP 800-204D by enabling cryptographic attestations and automated policy enforcement.
What Are Hadolint and Witness?
Hadolint is an open-source Dockerfile linter that checks for inefficiencies, security vulnerabilities, and bad practices in container configurations. It flags issues like running as root or using floating image tags, helping developers produce more secure and efficient container images.
Witness, on the other hand, enforces policies as code by collecting cryptographic attestations and verifying that specific steps in a pipeline comply with defined rules. By integrating Hadolint and Witness into a CI/CD pipeline, we ensure compliance is automatic, continuous, and baked into the development process.
Mapping Hadolint Rules to NIST SP 800-53 Controls
Hadolint’s linting rules map directly to several NIST SP 800-53 controls. Here’s how:
Enforcing Least Privilege (AC-6):
Control Description: Ensure applications and processes run with minimal necessary privileges.
Hadolint Rule: DL3002 - Avoid using the root user inside the container.
Implementation: Hadolint scans for instructions that might cause the container to run as root and flags them.
Benefit: Reduces the risk of privilege escalation attacks by enforcing least privilege.
Maintaining Baseline Configurations (CM-2):
Control Description: Develop and maintain baseline configurations for systems.
Hadolint Rule: DL3006 - Always tag the version of an image explicitly.
Implementation: Ensures images are built from specific base versions rather than floating tags like latest.
Benefit: Supports consistent and reproducible builds, essential for audits and rollbacks.
Implementing Least Functionality (CM-7):
Control Description: Configure systems to provide only essential capabilities.
Hadolint Rule: DL3009 - Delete the apt cache after installing something.
Implementation: Encourages the removal of unnecessary files and packages from container images.
Benefit: Reduces the attack surface by keeping images lean.
Enforcing Compliance with Witness Policies
Witness allows us to go beyond linting by enforcing policies at every step of the pipeline. Let’s walk through how to implement a portion of the Witness policy for the lint step. See the full policy here
{
"steps": {
"lint": {
"name": "lint",
"attestations": [
{
"type": "https://witness.dev/attestations/command-run/v0.1",
"regopolicies": [
{
"name": "expected command",
"module": "cGFja2FnZSBjb21tYW5kcnVuLmNtZAoKZGVueVttc2ddIHsKCWlucHV0LmNtZCAhPSBbIi9iaW4vc2giLCAiLWMiLCAiaGFkb2xpbnQgLWYgc2FyaWYgRG9ja2VyZmlsZSA+IGhhZG9saW50LnNhcmlmIl0KCW1zZyA6PSAidW5leHBlY3RlZCBjbWQiCn0K"
}
]
}
]
}
}
}
Decoding the Base64-Encoded Rego
The module field contains Base64-encoded Rego code. Decoded, it looks like this:
package commandrun.cmd
deny[msg] {
input.cmd != ["/bin/sh", "-c", "hadolint --disable-ignore-pragma -f sarif Dockerfile > hadolint.sarif"]
msg := "unexpected cmd"
}
What Does This Policy Do?
This Rego policy enforces that the exact command /bin/sh -c "hadolint -f sarif --disable-ignore-pragma Dockerfile > hadolint.sarif"
is executed in the lint step.
If the command differs: The pipeline denies the step and logs the message “unexpected cmd.”
Why it matters: This ensures no command injection or policy bypass can occur, maintaining strict control over the configuration of the security tool, ensuring compliance for every artifact, every time.
Witness Policies and NIST SP 800-204D
NIST SP 800-204D emphasizes automation and cryptographic assurance in securing software pipelines. Witness implements these principles by:
Automating Policy Enforcement: Ensures policies are enforced consistently and automatically.
Providing Cryptographic Assurance: Verifies the authenticity of each attestation collected during the pipeline.
Enabling Continuous Compliance: Tracks compliance at every step of the pipeline in real time.
CI/CD Integration Example
Here’s an example of integrating Hadolint and Witness into a GitHub Actions workflow using the Witness Run GitHub action:
lint:
uses: testifysec/witness-run-action/.github/workflows/witness.yml@reusable-workflow
with:
pull_request: ${{ github.event_name == 'pull_request' }}
step: lint
pre-command-attestations: "git github environment"
attestations: "git github environment"
archivista-server: "https://platform.testifysec.dev"
pre-command: |
curl -sSfL https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 -o /usr/local/bin/hadolint && \
chmod +x /usr/local/bin/hadolint
command: hadolint -f sarif --disable-ignore-pragma Dockerfile \
> hadolint.sarif
artifact-upload-name: hadolint.sarif
artifact-upload-path: hadolint.sarif
Why Use Witness Policies?
Exact Command Enforcement: Prevents unauthorized or unexpected commands.
Audit Trails: Automatically collects evidence for compliance audits.
Automation: Reduces manual oversight by integrating compliance checks into pipelines.
Alignment with NIST SP 800-204D: Implements secure pipeline practices outlined in the framework.
Verifying artifact compliance
Now that we have both the policy created and the attestation for the final artifact, we can validate that the artifact went through the correct process. We pass in the key material, the signed policy, and the artifact's location we want to verify. In this case, we use the TestifySec platform to store and serve the attestations.
witness verify -p policy-signed.json -k swfpublic.pem -f /tmp/image.tar --enable-archivista --archivista-server https://platform.testifysec.dev -l debug
When this command is run, it will generate an SLSA Verification Summary Attestation (VSA) with a pass or fail status. The attestation will include references to the policy and the evidence used to evaluate the policy. If the verification fails Witness will output detailed information to help debug the cause
Shifting Compliance Left
By integrating Hadolint and Witness into our CI/CD pipelines, we shift compliance left—embedding it directly into the development process. This approach:
Automates compliance, reducing manual intervention.
Enhances security by preventing misconfigurations and unauthorized changes.
Provides real-time evidence of compliance, simplifying audits and regulatory reviews.
This series is just getting started. Next, we’ll explore how Witness policies can enforce other NIST SP 800-53 controls and dive deeper into the principles of NIST SP 800-204D. If you’re interested in trying Witness, check out this step-by-step guide.
References:
Witness: https://witness.dev
SWF Repo (Full Example): https://github.com/testifysec/swf
Getting Started with Witness: https://testifysec.com/blog/attestations-with-witness
Hadolint: https://github.com/hadolint/hadolint
NIST SP 800-53: https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final
NIST SP 800-204D: https://csrc.nist.gov/publications/detail/sp/800-204d/final