Write Custom Checkov Policies in Python
Checkov ships hundreds of built-in checks, but your organisation has rules no vendor knows about. This guide, part of security and compliance basics under Python IaC fundamentals and strategy, writes a custom Checkov policy in Python and runs it against your infrastructure alongside the built-ins covered in scanning Python IaC with Checkov.
Context
A built-in scan enforces community baselines, but internal rules — an approved AMI list, a mandatory logging bucket, a naming convention — need custom checks. Checkov lets you write these as Python classes that plug into the same scan, so one command enforces both community and in-house policy.
Prerequisites
- Python 3.9+ with
checkovinstalled - A directory of Terraform/CDKTF-synthesized JSON to scan
- A rule expressed concretely: which resource, which attribute, what value is compliant
# CLI: confirm Checkov and point it at a synthesized config
checkov --version && ls cdktf.out/stacks/*/cdk.tf.json
Implementation
Subclass BaseResourceCheck, declare the resource types it applies to, and implement scan_resource_conf returning PASSED or FAILED. This check requires S3 buckets to have access logging configured.
# checks/s3_logging.py — require S3 access logging
# CLI: checkov -d . --external-checks-dir checks
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class S3AccessLogging(BaseResourceCheck):
def __init__(self):
super().__init__(
name="Ensure S3 buckets enable access logging",
id="CKV_ACME_1",
categories=[CheckCategories.LOGGING],
supported_resources=["aws_s3_bucket"])
def scan_resource_conf(self, conf: dict) -> CheckResult:
# conf holds the resource block; 'logging' must be present and non-empty.
logging = conf.get("logging")
return CheckResult.PASSED if logging else CheckResult.FAILED
check = S3AccessLogging()
Verification
Run Checkov with your external checks directory and confirm your custom id appears in the results, failing a non-compliant bucket.
# CLI: expect CKV_ACME_1 in the output, FAILED for an unlogged bucket
checkov -d cdktf.out --external-checks-dir checks --check CKV_ACME_1
Gotchas & Edge Cases
Attribute shape varies. Terraform wraps block attributes in lists; conf.get('logging') may be [{...}], so inspect the real conf before asserting on it.
Unique ids matter. Custom ids must not collide with built-ins; prefix them (CKV_ACME_*) so upgrades never clash.
Wire it into CI. A custom check only protects you if it runs on every change; add the --external-checks-dir flag to the same pipeline stage that runs the built-in scan.
FAQ
Can I write checks for CDKTF output?
Yes — Checkov scans the synthesized cdk.tf.json, so a check on aws_s3_bucket works whether the JSON came from HCL or CDKTF.
How do I suppress a check on one resource?
Add a checkov:skip comment or use a Checkov config file; document why, so suppressions stay auditable.
Is this a replacement for CrossGuard?
They overlap but differ: Checkov scans static config, while Pulumi CrossGuard evaluates Pulumi's planned graph at preview.
Related
- Scanning Python IaC with Checkov — running the built-in checks this guide extends.
- Security & Compliance Basics — the parent cluster on securing Python IaC.
- Pulumi Policy as Code with CrossGuard — the Pulumi-native alternative for preview-time enforcement.