Governance-as-code for the cloud — write YAML policies that find and fix non-compliant resources (untagged instances, public S3 buckets, idle volumes, unencrypted databases) across AWS, Azure, and GCP. You'll install it, write filter/action policies, run them in dry-run, automate them with events and schedules, and adopt guardrail patterns. Hands-on throughout.
The journey:
| You need | Why |
|---|---|
| Python 3.11+ | Custodian (c7n) is a Python tool |
| Cloud credentials (read-only to start) | It queries your account |
| A test/sandbox account | Never learn remediation on production |
Use a read-only role first and always --dryrun. Confirm the AWS basics in the AWS tutorial. Install: pip install c7n (add c7n-org for multi-account).
Cloud accounts drift out of compliance constantly: someone launches an untagged instance, opens a security group to the world, leaves a volume unattached burning money, or creates an unencrypted bucket. Clicking through the console to find and fix these doesn't scale.
Cloud Custodian ("c7n") turns governance into declarative YAML policies that run against your cloud API: find resources matching these filters, then take these actions. One tool, one language, across AWS/Azure/GCP — for tag compliance, cost control, security remediation, and cleanup. It's the automated policy enforcement layer for your cloud, run on a schedule or on events.
python3 -m venv .venv && source .venv/bin/activate
pip install c7n
A policy is YAML. This one finds EC2 instances missing an Owner tag (read-only — no action yet):
# policy.yml
policies:
- name: ec2-missing-owner-tag
resource: aws.ec2
filters:
- "tag:Owner": absent
custodian run --dryrun -s output policy.yml # never mutates in dry-run
custodian report -s output policy.yml # tabular view of matches
custodian run queries the account; -s output writes results and logs; report shows what matched. Start every policy this way — see what it selects before adding actions.
Every policy is three things:
policies:
- name: stop-untagged-instances
resource: aws.ec2 # 1. WHAT kind of resource
filters: # 2. WHICH ones (AND-ed)
- "tag:Owner": absent
- "State.Name": running
actions: # 3. WHAT to do to them
- type: stop
resource — the cloud resource type (aws.ec2, aws.s3, aws.rds, azure.vm, gcp.instance…). Hundreds are supported.filters — predicates that narrow the set (all must match).actions — what to do to the matches (stop, tag, delete, notify, encrypt…).The mental model is exactly find these, do that. Filters without actions = an audit report; add actions and it becomes enforcement.
Filters are the expressive core. Beyond simple tag/attribute checks:
filters:
- or: # boolean logic
- "tag:Environment": absent
- "tag:Owner": absent
- type: value # arbitrary attribute comparison
key: LaunchTime
op: less-than
value_type: age
value: 90 # older than 90 days
- type: metrics # CloudWatch metrics
name: CPUUtilization
statistics: Average
period: 86400
value: 5
op: less-than # avg CPU < 5% (idle)
Common filter types: value (any attribute with ops like less-than, regex, in), metrics (CloudWatch — find idle resources), age, security-group, image-age, and cross-resource filters. This is how you find "public buckets," "unencrypted RDS," or "instances idle for a week."
Actions turn findings into fixes:
policies:
- name: tag-compliance-mark-and-stop
resource: aws.ec2
filters:
- "tag:Owner": absent
actions:
- type: mark-for-op # tag it now, act later (grace period)
op: stop
days: 4
- type: notify # tell someone
to: [[email protected]]
transport: { type: sns, topic: arn:aws:sns:...:governance }
Powerful actions include stop/terminate, tag/remove-tag, set-encryption, remove-statements (fix a bucket policy), and mark-for-op — a two-phase pattern that tags a resource with a deadline, notifies the owner, and only remediates if it's still non-compliant after the grace period. That grace period is what makes automated remediation safe.
Remediation on the cloud is irreversible — roll out like any enforcement:
--dryrun always first — it reports matches and intended actions without touching anything.tag/notify/mark-for-op before stop/delete.custodian run --dryrun -s out policy.yml && custodian report -s out policy.yml
The classic disaster is a too-broad filter with a terminate action run for real — dry-run and soft actions exist precisely to prevent it.
A policy's mode decides when it runs:
schedule: "rate(1 day)").policies:
- name: block-public-bucket
resource: aws.s3
mode:
type: cloudtrail
events: [{ source: s3.amazonaws.com, event: PutBucketAcl, ids: "requestParameters.bucketName" }]
filters:
- type: global-grants # bucket granted to "everyone"
actions:
- type: set-permissions
remove: [Read, ReadAcp]
Event mode is real-time guardrails; periodic mode is scheduled sweeps; pull mode is ad-hoc audits and CI checks. Custodian deploys the Lambda + event wiring for you.
The three workhorse use cases:
Owner/Environment/CostCenter; mark-for-op and notify, then stop. Makes cost allocation and ownership real.0.0.0.0/0, unencrypted RDS/EBS, and disabled logging; fix or alert in real time via cloudtrail mode.These encode the guardrails from the AWS tutorial — private-by-default, least privilege, tagged, cost-aware — as continuously-enforced policy instead of hope.
--dryrun first, every time, and read the report before enabling actions.mark-for-op/notify with a grace period, then stop/delete.c7n-org to run across many accounts/regions from one place.terminate for real is a self-inflicted outage.report first.mark-for-op + notify so owners can react.Owner tag; run --dryrun and report.metrics filter for average CPU < 5% over a day to find idle instances.mark-for-op (stop after 4 days) + notify; confirm the tag appears (in a sandbox).cloudtrail-mode policy that flags a newly-public S3 bucket.Self-check:
mark-for-op safer than a direct stop/terminate?You now have the loop: resource → filters → actions → dry-run → soft-then-hard → automate with modes. That's cloud governance as continuously-enforced code.