Automated Compliance Checks

Production engineering guide for automated compliance checks covering patterns, implementation strategies, and operational best practices.

Automated compliance checks ensure that systems, configurations, and processes consistently meet predefined standards—security policies, operational guidelines, and regulatory requirements—by executing repeatable, machine-driven validation across infrastructure, services, and workflows. These checks run unattended, either on a schedule, in response to changes, or as part of deployment pipelines, and trigger alerts or actions when deviations occur. They are essential in environments where drift, configuration inconsistency, or audit readiness are critical concerns, such as in regulated industries, cloud-native platforms, or high-velocity engineering teams.

Validating System State Against a Policy

The core task is to verify that a system’s current state matches a desired configuration. This involves comparing actual values—filesystem permissions, service status, network rules, runtime environment variables—with a set of expected values defined in a policy.

A policy is a declarative specification that defines what must be true. It consists of a set of rules, each with a condition and an expected outcome. For example:

- name: Ensure SSH is enabled and restricted
  rules:
    - path: /etc/ssh/sshd_config
      key: PermitRootLogin
      expected: "no"
      description: "Root login via SSH must be disabled"
    - path: /etc/ssh/sshd_config
      key: PasswordAuthentication
      expected: "no"
      description: "Password authentication must be disabled"
    - service: sshd
      state: running
      enabled: true
      description: "SSH service must be running and enabled on boot"

A compliance checker reads this policy and applies it to a target system. The checker evaluates every rule, compares actual values against the expected ones, and produces a result per rule: pass, fail, or warning.

Running Checks on a Single Host

To validate a host, the checker executes commands locally or over SSH. It collects actual data and checks it against the policy.

On a Linux host, a minimal compliance check may use systemctl, grep, awk, and test in a shell script:

#!/bin/sh


POLICY_FILE="/etc/compliance/policy.yaml"
OUTPUT_DIR="/var/log/compliance"

# Load policy
policy() {
    local key="$1"
    yq -r ".rules[] | select(.key == \"$key\") | .expected" "$POLICY_FILE"
}

# Check SSH config
check_ssh_config() {
    local expected=$(policy "PermitRootLogin")
    local actual=$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}')
    if [ "$actual" = "$expected" ]; then
        echo "PASS: PermitRootLogin = $actual"
    else
        echo "FAIL: Expected $expected, got $actual"
        return 1
    fi
}

# Check SSH service
check_ssh_service() {
    local expected_state=$(policy "state")
    local expected_enabled=$(policy "enabled")

    local actual_state=$(systemctl is-active sshd)
    local actual_enabled=$(systemctl is-enabled sshd)

    if [ "$actual_state" = "$expected_state" ] && [ "$actual_enabled" = "$expected_enabled" ]; then
        echo "PASS: Service state and enabled match"
    else
        echo "FAIL: Expected state=$expected_state, enabled=$expected_enabled; got state=$actual_state, enabled=$actual_enabled"
        return 1
    fi
}

# Main execution
for rule in $(yq -r '.rules[].key' "$POLICY_FILE"); do
    check_ssh_config "$rule"
done
check_ssh_service

# Write results to JSON
{
    "timestamp": "$(date -Iseconds)",
    "host": "$(hostname)",
    "checks": [
        {
            "rule": "PermitRootLogin",
            "status": "pass",
            "actual": "$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}')",
            "expected": "$(policy "PermitRootLogin")"
        },
        {
            "rule": "PasswordAuthentication",
            "status": "fail",
            "actual": "$(grep "^PasswordAuthentication" /etc/ssh/sshd_config | awk '{print $2}')",
            "expected": "$(policy "PasswordAuthentication")"
        }
    ]
} | jq -c . > "$OUTPUT_DIR/results-$(date +%s).json"

When this script runs, it outputs structured results. A failure in PasswordAuthentication is immediately visible in logs and alerting systems. The jq -c output is consumed by monitoring tools and CI/CD pipelines.

Handling Drift and Reporting

Drift—differences between expected and actual states—must be detected and reported. A compliance runner maintains a history of checks, comparing current results with previous ones.

On a CI system, a job runs check-compliance.sh and uploads results to a central store:

# In CI pipeline
git clone https://github.com/org/compliance-policies.git /tmp/policies
./check-compliance.sh
curl -X POST \
     -H "Content-Type: application/json" \
     -d @/var/log/compliance/results-1712345678.json \
     https://compliance-api.example.com/v1/results

The API expects a JSON payload with a timestamp, host, and an array of checks. Each check has rule, status, actual, expected, and optional details.

If a check fails, the runner marks the entire compliance check as failed and sends a webhook to a notification system:

{
  "event": "compliance-check-failed",
  "timestamp": "2024-04-05T12:34:56Z",
  "host": "web-01.prod.example.com",
  "policy": "prod-ssh-standards.yaml",
  "results": [
    {
      "rule": "PasswordAuthentication",
      "status": "fail",
      "actual": "yes",
      "expected": "no",
      "details": "Config file has PasswordAuthentication yes, but policy requires no."
    }
  ],
  "summary": "1 of 3 rules failed"
}

The details field is critical: it captures the full context of a failure, such as the actual file content, command output, or system logs, and is used for debugging.

Enforcing Compliance in a Pipeline

Compliance checks are not just passive validations—they are enforcers. They can block workflows, trigger remediation, or require approvals.

In a deployment pipeline, a step runs a compliance check before promoting a service to production. If any rule fails, the pipeline halts.

The pipeline uses curl and jq to evaluate results and conditionally fail:

# After deploying a new version
COMPLIANCE_RESULT=$(curl -s https://compliance-api.example.com/v1/results/latest?host=web-01)

# Extract all failed checks
FAILED_CHECKS=$(echo "$COMPLIANCE_RESULT" | jq -c 'select(.results[].status == "fail") | .results[]')

if [ -n "$FAILED_CHECKS" ]; then
    echo "Compliance checks failed:"
    echo "$FAILED_CHECKS" | jq -r '.rule + ": " + .actual + " (expected: " + .expected + ")"'
    exit 1
fi

The exit 1 causes the CI/CD system to mark the build as failed. The jq -r output is logged and visible in the deployment dashboard.

Policy-Driven Remediation

When compliance fails, the system can automatically apply fixes—remediation. Remediation is a set of actions that bring a system back into compliance.

A remediation script applies changes based on the policy and the actual state:

#!/bin/sh
# apply-remediation.sh

POLICY_FILE="/etc/compliance/policy.yaml"
RESULTS_FILE="/var/log/compliance/results.json"

# Read failures
failed_rules=$(jq -r '.results[] | select(.status == "fail") | .rule' "$RESULTS_FILE")

for rule in $failed_rules; do
    case "$rule" in
        "PermitRootLogin")
            sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
            systemctl restart sshd
            echo "Remediated: PermitRootLogin = no"
            ;;
        "PasswordAuthentication")
            sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
            systemctl restart sshd
            echo "Remediated: PasswordAuthentication = no"
            ;;
        *)
            echo "Unknown rule: $rule, skipping remediation"
            ;;
    esac
done

# Re-run compliance to verify fixes
./check-compliance.sh

This script uses sed to modify configuration files, systemctl to restart services, and re-runs the full check. It is idempotent: running it multiple times does not introduce side effects.

Remediation can be triggered manually or automatically. A cron job runs it hourly:

# /etc/cron.d/compliance-remediation
0 * * * * root /opt/compliance/apply-remediation.sh

If the remediation fails, the pipeline receives a compliance-remediation-failed event and sends an alert.

Monitoring and Drift Detection

Compliance checks are not one-off tasks. They must run continuously and produce trends over time.

A monitoring system ingests results and plots compliance status per host and per rule. It detects drift by comparing current results to historical ones.

Each check result includes a timestamp and host. The system queries for results over time:

SELECT
    host,
    rule,
    COUNT(*) AS total_checks,
    SUM(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) AS passes,
    SUM(CASE WHEN status = 'fail' THEN 1 ELSE 0 END) AS failures,
    AVG(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) AS pass_rate
FROM compliance_results
WHERE timestamp >= NOW() - INTERVAL '7 days'
GROUP BY host, rule
ORDER BY pass_rate ASC;

This query identifies hosts where PasswordAuthentication has been failing for multiple days, indicating a systemic issue.

Silent Failures and Edge Cases

Several subtle issues often go unnoticed:

Aggregating Results and Generating Reports

Compliance data is valuable beyond individual checks. Aggregated reports show compliance trends across the infrastructure.

A weekly report generates a summary:

#!/bin/sh
# generate-compliance-report.sh

OUTPUT_DIR="/var/reports/compliance"
DATE=$(date +%Y-%m-%d)

# Fetch all results from last 7 days
curl -s https://compliance-api.example.com/v1/results?since=7d | jq -c . > "$OUTPUT_DIR/results-$DATE.json"

# Compute pass rate per host
jq -r '
  .results[] |
  select(.timestamp > "2024-04-01T00:00:00Z") |
  {host, status, rule} |
  group_by(.host) |
  map(
    {
      host: .[0].host,
      total: length,
      passed: [ .[] | select(.status == "pass") | .rule ] | length,
      failed: [ .[] | select(.status == "fail") | .rule ] | length,
      pass_rate: (.[].passed / .[].total * 100) | round(2)
    }
  ) |
  sort_by(.pass_rate) |
  .[] |
  "\(.host): \(.pass_rate)% pass rate (\(.passed)/\(.total))"
' "$OUTPUT_DIR/results-$DATE.json"

This report is sent via email or displayed in a dashboard. It reveals which hosts are lagging, which policies are most frequently violated, and which teams are responsible for which compliance gaps.

Compliance checks are not just about checking boxes. They are a continuous, self-correcting process where every failure is an opportunity to improve, and every pass is a signal of reliability.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.