Shift-left tag enforcement for Terraform PRs — Catch missing or invalid tags before they reach production.
Untagged AWS resources cost organisations money and create compliance gaps. This action:
- Prevents untagged resources from being deployed by failing PRs with missing tags
- Enforces consistency across teams by validating against a defined tag policy
- Reduces remediation costs by catching issues at PR time, not after deployment
- Supports FinOps and compliance by ensuring resources are properly attributed
- Works with modules: Validates tags on module-created resources via Terraform plan
- Detects empty values: Catches null, empty, or whitespace-only tag values
- Supports default_tags: Works with AWS provider
default_tagsviatags_all - Configurable: Specify your own required tags or use MoJ defaults
This action uses terraform_plan framework, not static file scanning. This means it can:
| Capability | Standard Checkov | This Action |
|---|---|---|
See tags_all (merged default_tags + resource tags) |
❌ | ✅ |
| Validate module-created resources | ❌ | ✅ |
| Detect whitespace-only tag values | ❌ | ✅ |
Note: This action is designed to run alongside your existing Checkov workflows, not replace them. Standard Checkov handles security scanning; this action focuses specifically on tag enforcement.
Create .github/workflows/validate-tags.yml:
name: Validate Tags
on:
pull_request:
paths:
- '**/*.tf'
permissions:
contents: read
pull-requests: write
jobs:
validate-tags:
name: Tag Validation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate Tags
id: validate
uses: FolarinOyenuga/checkov-tag-validator@v1
with:
terraform_directory: ./terraform
required_tags: |
business-unit
application
owner
is-production
service-area
environment
soft_fail: false
- name: Post Results to PR
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
SUMMARY: ${{ steps.validate.outputs.violations_summary }}
PASSED: ${{ steps.validate.outputs.passed }}
with:
script: |
const summary = process.env.SUMMARY || '✅ All resources have required tags';
const passed = process.env.PASSED === 'true';
const icon = passed ? '✅' : '⚠️';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🏷️ Tag Validation ${icon}\n\n${summary}`
});| Input | Description | Required | Default |
|---|---|---|---|
terraform_directory |
Path to Terraform files | Yes | . |
required_tags |
List of required tag keys (newline or comma-separated) | Yes | MoJ defaults |
framework |
Checkov framework to use | No | terraform |
soft_fail |
Return exit code 0 even if violations found | No | false |
| Output | Description |
|---|---|
violations_count |
Number of tag violations found |
violations_summary |
Markdown summary for PR comments |
passed |
Whether validation passed (true/false) |
If not specified, the following MoJ tags are required:
business-unitapplicationowneris-productionservice-areaenvironment
- Generates Terraform plan: Runs
terraform initandterraform planwith dummy credentials - Scans plan with Checkov: Uses custom policy to check
tags_allon all resources - Reports violations: Outputs violation count, pass/fail status, and markdown summary
- Add the workflow file to
.github/workflows/validate-tags.yml - Ensure your Terraform can
initwithout real credentials (usebackend = false) - Configure required tags for your team's needs
- Optionally add PR comment integration for visibility
When violations are detected:
- The action exits with code 1 (fails the check)
- The
violations_summaryoutput contains markdown-formatted details - Use
soft_fail: trueto report violations without failing the build
- Fork the repository
- Create a feature branch (
git checkout -b my-feature) - Run linting locally:
ruff check . && ruff format --check . - Commit and push your changes
- Open a pull request
MIT