tf-nag 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tf_nag-0.1.0/.github/scripts/open_sync_pr.sh +48 -0
- tf_nag-0.1.0/.github/workflows/release.yml +104 -0
- tf_nag-0.1.0/.github/workflows/upstream-sync.yml +94 -0
- tf_nag-0.1.0/.gitignore +2 -0
- tf_nag-0.1.0/PKG-INFO +6 -0
- tf_nag-0.1.0/README.md +196 -0
- tf_nag-0.1.0/docs/rule-mapping.md +137 -0
- tf_nag-0.1.0/pyproject.toml +36 -0
- tf_nag-0.1.0/scripts/generate_rule_mapping.py +29 -0
- tf_nag-0.1.0/scripts/sync_upstream.py +152 -0
- tf_nag-0.1.0/tests/fixtures/clean-plan.json +1 -0
- tf_nag-0.1.0/tests/fixtures/minimal-plan.json +35 -0
- tf_nag-0.1.0/tests/fixtures/state.json +9 -0
- tf_nag-0.1.0/tests/fixtures/warn-plan.json +1 -0
- tf_nag-0.1.0/tests/real-terraform/.terraform.lock.hcl +25 -0
- tf_nag-0.1.0/tests/real-terraform/main.tf +67 -0
- tf_nag-0.1.0/tests/real-terraform/modules/web/main.tf +16 -0
- tf_nag-0.1.0/tests/real-terraform/real-plan.json +1 -0
- tf_nag-0.1.0/tests/test_rule_architecture.py +101 -0
- tf_nag-0.1.0/tests/test_rule_fixtures.py +1102 -0
- tf_nag-0.1.0/tests/test_suppressions.py +243 -0
- tf_nag-0.1.0/tests/test_tf_nag.py +614 -0
- tf_nag-0.1.0/tfnag/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/cli.py +58 -0
- tf_nag-0.1.0/tfnag/data/aws_solutions_pack.json +1367 -0
- tf_nag-0.1.0/tfnag/data/lambda_runtimes.json +16 -0
- tf_nag-0.1.0/tfnag/data/provider_schema.json +25796 -0
- tf_nag-0.1.0/tfnag/engine.py +70 -0
- tf_nag-0.1.0/tfnag/graph.py +242 -0
- tf_nag-0.1.0/tfnag/loader.py +196 -0
- tf_nag-0.1.0/tfnag/model.py +125 -0
- tf_nag-0.1.0/tfnag/registry.py +78 -0
- tf_nag-0.1.0/tfnag/reporters.py +158 -0
- tf_nag-0.1.0/tfnag/rules/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/apigw/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/apigw/rules.py +128 -0
- tf_nag-0.1.0/tfnag/rules/appsync/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/appsync/rules.py +21 -0
- tf_nag-0.1.0/tfnag/rules/autoscaling/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/autoscaling/rules.py +67 -0
- tf_nag-0.1.0/tfnag/rules/cloud9/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/cloud9/rules.py +25 -0
- tf_nag-0.1.0/tfnag/rules/cloudfront/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/cloudfront/rules.py +130 -0
- tf_nag-0.1.0/tfnag/rules/codebuild/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/codebuild/rules.py +38 -0
- tf_nag-0.1.0/tfnag/rules/cognito/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/cognito/rules.py +103 -0
- tf_nag-0.1.0/tfnag/rules/common.py +227 -0
- tf_nag-0.1.0/tfnag/rules/documentdb/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/documentdb/rules.py +75 -0
- tf_nag-0.1.0/tfnag/rules/dynamodb/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/dynamodb/rules.py +29 -0
- tf_nag-0.1.0/tfnag/rules/ec2/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/rules/ec2/rules.py +140 -0
- tf_nag-0.1.0/tfnag/rules/ecr/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/ecr/rules.py +27 -0
- tf_nag-0.1.0/tfnag/rules/ecs/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/ecs/rules.py +81 -0
- tf_nag-0.1.0/tfnag/rules/eks/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/eks/rules.py +35 -0
- tf_nag-0.1.0/tfnag/rules/elasticache/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/elasticache/rules.py +85 -0
- tf_nag-0.1.0/tfnag/rules/elasticbeanstalk/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/elasticbeanstalk/rules.py +82 -0
- tf_nag-0.1.0/tfnag/rules/elb/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/rules/elb/rules.py +94 -0
- tf_nag-0.1.0/tfnag/rules/emr/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/emr/rules.py +103 -0
- tf_nag-0.1.0/tfnag/rules/eventbridge/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/eventbridge/rules.py +17 -0
- tf_nag-0.1.0/tfnag/rules/glue/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/glue/rules.py +45 -0
- tf_nag-0.1.0/tfnag/rules/iam/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/iam/rules.py +68 -0
- tf_nag-0.1.0/tfnag/rules/kinesis/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/kinesis/rules.py +72 -0
- tf_nag-0.1.0/tfnag/rules/lambda_rules.py +40 -0
- tf_nag-0.1.0/tfnag/rules/lex/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/lex/rules.py +28 -0
- tf_nag-0.1.0/tfnag/rules/mediastore/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/mediastore/rules.py +39 -0
- tf_nag-0.1.0/tfnag/rules/messaging/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/rules/messaging/rules.py +89 -0
- tf_nag-0.1.0/tfnag/rules/msk/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/msk/rules.py +58 -0
- tf_nag-0.1.0/tfnag/rules/neptune/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/neptune/rules.py +72 -0
- tf_nag-0.1.0/tfnag/rules/network/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/rules/network/rules.py +73 -0
- tf_nag-0.1.0/tfnag/rules/opensearch/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/opensearch/rules.py +144 -0
- tf_nag-0.1.0/tfnag/rules/quicksight/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/quicksight/rules.py +25 -0
- tf_nag-0.1.0/tfnag/rules/rds/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/rules/rds/rules.py +196 -0
- tf_nag-0.1.0/tfnag/rules/redshift/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/redshift/rules.py +176 -0
- tf_nag-0.1.0/tfnag/rules/s3/__init__.py +3 -0
- tf_nag-0.1.0/tfnag/rules/s3/rules.py +112 -0
- tf_nag-0.1.0/tfnag/rules/sagemaker/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/sagemaker/rules.py +61 -0
- tf_nag-0.1.0/tfnag/rules/secretsmanager/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/secretsmanager/rules.py +30 -0
- tf_nag-0.1.0/tfnag/rules/stepfunctions/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/stepfunctions/rules.py +38 -0
- tf_nag-0.1.0/tfnag/rules/timestream/__init__.py +1 -0
- tf_nag-0.1.0/tfnag/rules/timestream/rules.py +19 -0
- tf_nag-0.1.0/tfnag/schema.py +55 -0
- tf_nag-0.1.0/tfnag/suppressions.py +241 -0
- tf_nag-0.1.0/tfnag/sync/__init__.py +18 -0
- tf_nag-0.1.0/tfnag/sync/pack.py +134 -0
- tf_nag-0.1.0/tfnag/sync/provider.py +63 -0
- tf_nag-0.1.0/tfnag/sync/report.py +49 -0
- tf_nag-0.1.0/tfnag/sync/runtimes.py +118 -0
- tf_nag-0.1.0/uv.lock +108 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Push the refreshed vendored data and open (or update) the upstream-sync PR.
|
|
3
|
+
#
|
|
4
|
+
# Expects to run from an actions/checkout working tree with GH_TOKEN set to a
|
|
5
|
+
# token that can push and open pull requests. Requires "Allow GitHub Actions
|
|
6
|
+
# to create and approve pull requests" enabled for the repo. Sets the step
|
|
7
|
+
# outputs prUrl and prId. Only runs when the sync step reported drift; the
|
|
8
|
+
# check below is a safety net for manual invocation.
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
BRANCH="${SYNC_BRANCH:?SYNC_BRANCH is required}"
|
|
12
|
+
TARGET="${TARGET_BRANCH:?TARGET_BRANCH is required}"
|
|
13
|
+
SUMMARY_FILE="${SUMMARY_FILE:?SUMMARY_FILE is required}"
|
|
14
|
+
TITLE="chore: sync upstream AWS Solutions pack and AWS runtime data"
|
|
15
|
+
|
|
16
|
+
if [[ -z "$(git status --porcelain -- tfnag/data)" ]]; then
|
|
17
|
+
echo "No upstream drift; nothing to push."
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
git config user.email "noreply@github.com"
|
|
22
|
+
git config user.name "github-actions[bot]"
|
|
23
|
+
git checkout -B "$BRANCH"
|
|
24
|
+
git add tfnag/data
|
|
25
|
+
git commit -m "$TITLE" -m "Generated by the weekly upstream-sync workflow."
|
|
26
|
+
# The branch is regenerated from the target branch on every run, so the remote
|
|
27
|
+
# copy of a superseded sync is replaced rather than merged into.
|
|
28
|
+
git push --force origin "HEAD:refs/heads/$BRANCH"
|
|
29
|
+
|
|
30
|
+
existing="$(gh pr list --head "$BRANCH" --base "$TARGET" --state open \
|
|
31
|
+
--json number --jq '.[0].number // empty')"
|
|
32
|
+
|
|
33
|
+
if [[ -n "$existing" ]]; then
|
|
34
|
+
gh pr edit "$existing" --body-file "$SUMMARY_FILE" >/dev/null
|
|
35
|
+
pr_id="$existing"
|
|
36
|
+
else
|
|
37
|
+
gh pr create --head "$BRANCH" --base "$TARGET" \
|
|
38
|
+
--title "$TITLE" --body-file "$SUMMARY_FILE" >/dev/null
|
|
39
|
+
pr_id="$(gh pr list --head "$BRANCH" --base "$TARGET" --state open \
|
|
40
|
+
--json number --jq '.[0].number')"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
pr_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${pr_id}"
|
|
44
|
+
echo "Sync PR: $pr_url"
|
|
45
|
+
{
|
|
46
|
+
echo "prId=$pr_id"
|
|
47
|
+
echo "prUrl=$pr_url"
|
|
48
|
+
} >> "$GITHUB_OUTPUT"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Build and publish tf-nag to PyPI when a version tag is pushed.
|
|
2
|
+
#
|
|
3
|
+
# Releases are cut deliberately by tagging a commit on master with `vX.Y.Z`
|
|
4
|
+
# (matching the version in pyproject.toml), which avoids duplicate-publish
|
|
5
|
+
# failures from firing on every merge. The publish job is pinned to the `pypi`
|
|
6
|
+
# GitHub Environment, which carries a required-reviewer protection rule: the
|
|
7
|
+
# build runs automatically, then the workflow pauses and waits for a manual
|
|
8
|
+
# approval before anything is uploaded to PyPI.
|
|
9
|
+
name: release
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
tags:
|
|
14
|
+
- 'v*'
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
env:
|
|
21
|
+
PYTHON_VERSION: '3.12'
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: release
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
# Build sdist + wheel and run the test suite. No secrets are needed here, so
|
|
29
|
+
# it stays outside the gated environment and gives reviewers a green artifact
|
|
30
|
+
# to approve.
|
|
31
|
+
build:
|
|
32
|
+
name: Build distribution
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
|
|
37
|
+
- uses: actions/setup-python@v7
|
|
38
|
+
with:
|
|
39
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
40
|
+
|
|
41
|
+
- name: run_checks
|
|
42
|
+
run: |
|
|
43
|
+
set -euo pipefail
|
|
44
|
+
python -m pip install --quiet pytest ruff
|
|
45
|
+
python -m pip install --quiet .
|
|
46
|
+
python -m pytest -q
|
|
47
|
+
python -m ruff check .
|
|
48
|
+
|
|
49
|
+
- name: build_dist
|
|
50
|
+
run: |
|
|
51
|
+
set -euo pipefail
|
|
52
|
+
python -m pip install --quiet build
|
|
53
|
+
python -m build
|
|
54
|
+
|
|
55
|
+
- name: upload_dist
|
|
56
|
+
uses: actions/upload-artifact@v7
|
|
57
|
+
with:
|
|
58
|
+
name: dist
|
|
59
|
+
path: dist/
|
|
60
|
+
|
|
61
|
+
# Gated on the `pypi` environment. Execution halts here until a required
|
|
62
|
+
# reviewer approves the deployment, then the stored API token is used to
|
|
63
|
+
# upload the artifact built above.
|
|
64
|
+
publish:
|
|
65
|
+
name: Publish to PyPI
|
|
66
|
+
needs: build
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
environment:
|
|
69
|
+
name: pypi
|
|
70
|
+
url: https://pypi.org/project/tf-nag/
|
|
71
|
+
steps:
|
|
72
|
+
- name: download_dist
|
|
73
|
+
uses: actions/download-artifact@v7
|
|
74
|
+
with:
|
|
75
|
+
name: dist
|
|
76
|
+
path: dist/
|
|
77
|
+
|
|
78
|
+
- name: publish_to_pypi
|
|
79
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
80
|
+
with:
|
|
81
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
82
|
+
|
|
83
|
+
# Publish a GitHub Release for the tag once the package is on PyPI, attaching
|
|
84
|
+
# the built sdist/wheel and auto-generated notes. Runs only for tag pushes;
|
|
85
|
+
# a manual workflow_dispatch has no tag to release against.
|
|
86
|
+
github_release:
|
|
87
|
+
name: Create GitHub Release
|
|
88
|
+
needs: publish
|
|
89
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
permissions:
|
|
92
|
+
contents: write
|
|
93
|
+
steps:
|
|
94
|
+
- name: download_dist
|
|
95
|
+
uses: actions/download-artifact@v7
|
|
96
|
+
with:
|
|
97
|
+
name: dist
|
|
98
|
+
path: dist/
|
|
99
|
+
|
|
100
|
+
- name: create_release
|
|
101
|
+
uses: softprops/action-gh-release@v2
|
|
102
|
+
with:
|
|
103
|
+
generate_release_notes: true
|
|
104
|
+
files: dist/*
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Weekly refresh of everything tf-nag vendors from upstream: the cdk-nag AWS
|
|
2
|
+
# Solutions pack, the latest AWS Lambda runtimes, and the AWS provider schema.
|
|
3
|
+
# Drift is pushed to a single sync branch and raised as a PR for review.
|
|
4
|
+
name: upstream-sync
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: '0 6 * * 1'
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
env:
|
|
16
|
+
PYTHON_VERSION: '3.12'
|
|
17
|
+
TERRAFORM_VERSION: '1.9.8'
|
|
18
|
+
SYNC_BRANCH: chore/upstream-sync
|
|
19
|
+
TARGET_BRANCH: master
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: upstream-sync
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
upstream_sync:
|
|
27
|
+
name: Refresh vendored upstream data
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v7
|
|
31
|
+
|
|
32
|
+
- uses: actions/setup-python@v7
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
35
|
+
|
|
36
|
+
- name: install_terraform
|
|
37
|
+
run: |
|
|
38
|
+
set -euo pipefail
|
|
39
|
+
curl -fsSL -o terraform.zip \
|
|
40
|
+
"https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip"
|
|
41
|
+
unzip -o -q terraform.zip -d "$RUNNER_TEMP/terraform"
|
|
42
|
+
echo "$RUNNER_TEMP/terraform" >> "$GITHUB_PATH"
|
|
43
|
+
|
|
44
|
+
# Everything downstream is gated on drift: a week where upstream did not
|
|
45
|
+
# move ends here.
|
|
46
|
+
- name: sync_upstream_sources
|
|
47
|
+
id: sync
|
|
48
|
+
run: |
|
|
49
|
+
set -euo pipefail
|
|
50
|
+
echo "SUMMARY_FILE=$RUNNER_TEMP/sync-summary.md" >> "$GITHUB_ENV"
|
|
51
|
+
python scripts/sync_upstream.py --summary-out "$RUNNER_TEMP/sync-summary.md"
|
|
52
|
+
if [[ -z "$(git status --porcelain -- tfnag/data)" ]]; then
|
|
53
|
+
echo "No upstream drift."
|
|
54
|
+
echo "drift=false" >> "$GITHUB_OUTPUT"
|
|
55
|
+
else
|
|
56
|
+
echo "drift=true" >> "$GITHUB_OUTPUT"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# The refreshed provider schema is what the rule metadata is validated
|
|
60
|
+
# against, so a failure here means a rule needs remapping by hand. The PR
|
|
61
|
+
# is still raised, with the failure recorded in its description.
|
|
62
|
+
- name: run_checks
|
|
63
|
+
id: validate
|
|
64
|
+
if: steps.sync.outputs.drift == 'true'
|
|
65
|
+
run: |
|
|
66
|
+
set -uo pipefail
|
|
67
|
+
python -m pip install --quiet pytest ruff
|
|
68
|
+
python -m pip install --quiet .
|
|
69
|
+
if python -m pytest -q && python -m ruff check . && python -m ruff format --check .; then
|
|
70
|
+
echo "validated=true" >> "$GITHUB_OUTPUT"
|
|
71
|
+
else
|
|
72
|
+
echo "validated=false" >> "$GITHUB_OUTPUT"
|
|
73
|
+
{
|
|
74
|
+
echo
|
|
75
|
+
echo "### Checks failed"
|
|
76
|
+
echo
|
|
77
|
+
echo "- \`pytest\`/\`ruff\` did not pass on the refreshed data."
|
|
78
|
+
echo "- See the [workflow logs](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})."
|
|
79
|
+
} >> "$SUMMARY_FILE"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
- name: open_sync_pr
|
|
83
|
+
id: pr
|
|
84
|
+
if: steps.sync.outputs.drift == 'true'
|
|
85
|
+
run: bash .github/scripts/open_sync_pr.sh
|
|
86
|
+
env:
|
|
87
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
88
|
+
|
|
89
|
+
- name: publish_summary
|
|
90
|
+
if: steps.sync.outputs.drift == 'true'
|
|
91
|
+
uses: actions/upload-artifact@v7
|
|
92
|
+
with:
|
|
93
|
+
name: sync-summary
|
|
94
|
+
path: ${{ env.SUMMARY_FILE }}
|
tf_nag-0.1.0/.gitignore
ADDED
tf_nag-0.1.0/PKG-INFO
ADDED
tf_nag-0.1.0/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# tf-nag
|
|
2
|
+
|
|
3
|
+
`tf-nag` is an offline Python CLI that checks Terraform plan or state JSON
|
|
4
|
+
against the tf-nag AWS Solutions rules. It does not contact AWS.
|
|
5
|
+
|
|
6
|
+
Rule IDs and severity levels are derived from the upstream AWS Solutions pack:
|
|
7
|
+
https://github.com/cdklabs/cdk-nag.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Clone this repository and install its development environment with `uv`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git clone git@github.com:vumdao/terraform-nag.git
|
|
15
|
+
cd terraform-nag
|
|
16
|
+
uv sync --group dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For a one-off invocation without installing globally:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uvx --from . tf-nag --version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For a regular virtual environment or pipeline installation:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m venv .venv
|
|
29
|
+
. .venv/bin/activate
|
|
30
|
+
pip install .
|
|
31
|
+
tf-nag --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Manual run (plan JSON)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
terraform plan -out=tf.plan -refresh=false -lock=false
|
|
38
|
+
terraform show -json tf.plan > plan.json
|
|
39
|
+
tf-nag scan --plan-json plan.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To scan a subdirectory or workspace, run the first two commands from that
|
|
43
|
+
directory (or select the workspace with `terraform workspace select NAME`) and
|
|
44
|
+
pass its resulting JSON path to `--plan-json`, for example:
|
|
45
|
+
`tf-nag scan --plan-json ./environments/prod/plan.json`.
|
|
46
|
+
The same workflow can be piped without an intermediate file:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
terraform show -json tf.plan | tf-nag scan --plan-json -
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Example run
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
$ terraform plan -out=tf.plan -refresh=false -lock=false
|
|
56
|
+
|
|
57
|
+
module.demo_custom_domain.data.aws_route53_zone.hosted_zone[0]: Reading...
|
|
58
|
+
module.demo_svc.module.demo_lambda.data.aws_iam_policy_document.lambda_assume_role: Reading...
|
|
59
|
+
module.demo_svc.module.demo_lambda.data.aws_iam_policy_document.lambda_assume_role: Read complete after 0s [id=1792567899]
|
|
60
|
+
module.demo_svc.module.demo_lambda.data.aws_iam_policy_document.lambda_policy: Reading...
|
|
61
|
+
module.demo_svc.module.demo_lambda.data.aws_iam_policy_document.lambda_policy: Read complete after 0s [id=1012345678]
|
|
62
|
+
module.demo_custom_domain.data.aws_route53_zone.hosted_zone[0]: Read complete after 2s [id=Z45F22PP7P3PP]
|
|
63
|
+
|
|
64
|
+
No changes. Your infrastructure matches the configuration.
|
|
65
|
+
|
|
66
|
+
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
|
|
67
|
+
|
|
68
|
+
⚡ $ terraform show -json tf.plan > plan.json
|
|
69
|
+
|
|
70
|
+
⚡ $ uvx --from /tmp/terraform-nag tf-nag scan --plan-json plan.json
|
|
71
|
+
LEVEL RULE ADDRESS MESSAGE
|
|
72
|
+
ERROR AwsSolutions-APIG1 module.demo_svc.module.demo_gw.aws_api_gateway_stage.demo-gw-stage The API does not have access logging enabled.
|
|
73
|
+
ERROR AwsSolutions-APIG2 module.demo_svc.aws_api_gateway_rest_api.demo-gw-api The REST API does not have request validation enabled.
|
|
74
|
+
WARN AwsSolutions-APIG3 module.demo_svc.module.demo_gw.aws_api_gateway_stage.demo-gw-stage The REST API stage is not associated with AWS WAFv2 web ACL.
|
|
75
|
+
ERROR AwsSolutions-APIG4 module.demo_svc.module.demo_gw.aws_api_gateway_method.demo-gw-method The API does not implement authorization.
|
|
76
|
+
ERROR AwsSolutions-APIG4 module.demo_svc.module.demo_gw.aws_api_gateway_method.demo-gw-options The API does not implement authorization.
|
|
77
|
+
ERROR AwsSolutions-COG4 module.demo_svc.module.demo_gw.aws_api_gateway_method.demo-gw-method The API GW method does not use a Cognito user pool authorizer.
|
|
78
|
+
WARN AwsSolutions-DDB3 module.demo_svc.module.demo_cached_table.aws_dynamodb_table.demo-ld-cached-table The DynamoDB table does not have Point-in-time Recovery enabled.
|
|
79
|
+
ERROR AwsSolutions-IAM5 module.demo_svc.module.demo_lambda.aws_iam_policy.demo-lambda-policy The IAM entity contains wildcard permissions and does not have a tf-nag rule suppression with evidence for those permission.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Exit codes are **0** for clean (or when `--fail-on never` is used), **1** when
|
|
83
|
+
an ERROR finding is present, and **2** for WARN-only findings when
|
|
84
|
+
`--fail-on warn` is selected. **3** indicates a malformed suppression
|
|
85
|
+
configuration. The default `--fail-on error` reports WARN-only findings but
|
|
86
|
+
exits 0.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
tf-nag scan --plan-json plan.json --format sarif
|
|
90
|
+
tf-nag list-rules
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The plan-JSON loader also accepts state JSON. HCL parsing is intentionally
|
|
94
|
+
deferred; this phase is fully offline and has no runtime dependencies.
|
|
95
|
+
|
|
96
|
+
## Suppressions
|
|
97
|
+
|
|
98
|
+
The canonical file is `.tfnag.json` in the current directory. Pass
|
|
99
|
+
`--suppressions PATH` to select another file. If neither is passed explicitly,
|
|
100
|
+
`.tfnag.json` takes precedence over the legacy `.tfnag.yml`.
|
|
101
|
+
|
|
102
|
+
The JSON file may be either an object containing a `suppressions` array or a
|
|
103
|
+
bare top-level array:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"suppressions": [
|
|
108
|
+
{
|
|
109
|
+
"id": "AwsSolutions-APIG1",
|
|
110
|
+
"resources": ["demo_gw.aws_api_gateway_stage.demo-gw-stage"],
|
|
111
|
+
"reason": "Access logging handled centrally"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`id` must be a registered rule ID. `resources` matches a full Terraform
|
|
118
|
+
address or a trailing suffix on whole `.`-segment boundaries, so the example
|
|
119
|
+
matches
|
|
120
|
+
`module.demo_svc.module.demo_gw.aws_api_gateway_stage.demo-gw-stage`, but not
|
|
121
|
+
`gw-stage` or `stage.demo-gw-stage`. `*` globs are supported within a segment.
|
|
122
|
+
An address without a `count`/`for_each` instance key matches every expanded
|
|
123
|
+
instance; an address with a key matches only that instance. Use
|
|
124
|
+
`"resources": ["*"]` to suppress every resource for that rule.
|
|
125
|
+
|
|
126
|
+
`reason` is mandatory and non-empty. `expires` is optional and must be
|
|
127
|
+
`YYYY-MM-DD`; expired entries do not suppress findings and are reported as
|
|
128
|
+
WARN with an expiration message. Suppressed findings remain visible in table,
|
|
129
|
+
Markdown, and JSON reports, including their reason. Table and Markdown
|
|
130
|
+
reports include a suppression summary and warn about entries that matched no
|
|
131
|
+
findings. `--strict` ignores all suppressions.
|
|
132
|
+
|
|
133
|
+
For back-compat, `.tfnag.yml` continues to support the restricted format below.
|
|
134
|
+
Create `.tfnag.yml` with one or more list items. The supported subset is
|
|
135
|
+
exactly a top-level sequence of mappings containing:
|
|
136
|
+
|
|
137
|
+
- `rule` (or `id`): an exact `AwsSolutions-*` ID, or `*`
|
|
138
|
+
- `address`: an optional shell-style glob, defaulting to `*`
|
|
139
|
+
- `reason`: a required non-empty string
|
|
140
|
+
- `expires`: an optional ISO date (`YYYY-MM-DD`)
|
|
141
|
+
|
|
142
|
+
For example:
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
- rule: AwsSolutions-S1
|
|
146
|
+
address: aws_s3_bucket.site
|
|
147
|
+
reason: Access logs are delivered by the centralized platform account.
|
|
148
|
+
expires: 2027-01-01
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Address values are shell-style globs. Expired suppressions are not applied.
|
|
152
|
+
Unsupported YAML is silently ignored by the restricted parser: nested mappings,
|
|
153
|
+
flow-style lists, quoted multiline values, and unrelated top-level mappings are
|
|
154
|
+
not supported. `--strict` ignores all suppressions. Inline HCL comments are
|
|
155
|
+
deferred until the HCL loader exists (TODO).
|
|
156
|
+
|
|
157
|
+
## Rule coverage
|
|
158
|
+
|
|
159
|
+
`tf-nag list-rules` reads the vendored authoritative inventory and reports all
|
|
160
|
+
131 AWS Solutions rules:
|
|
161
|
+
|
|
162
|
+
| Status | Count |
|
|
163
|
+
| --------------------------- | ----: |
|
|
164
|
+
| implemented | 126 |
|
|
165
|
+
| not-applicable-to-terraform | 5 |
|
|
166
|
+
| not-implemented | 0 |
|
|
167
|
+
|
|
168
|
+
The not-applicable rules have no Terraform provider resource equivalent.
|
|
169
|
+
Run `tf-nag list-rules` for the complete per-rule table.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
uv sync --group dev
|
|
175
|
+
uv run ruff check .
|
|
176
|
+
uv run ruff format --check .
|
|
177
|
+
uv run pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Fixtures in `tests/fixtures` are hand-crafted documents matching the
|
|
181
|
+
`terraform show -json` schema. `tests/real-terraform/real-plan.json` is
|
|
182
|
+
generated by Terraform 1.9.8 with the AWS provider and is committed as a
|
|
183
|
+
real-plan integration fixture; it needs no AWS credentials to scan. Terraform
|
|
184
|
+
is not required to run the suite.
|
|
185
|
+
`python scripts/generate_rule_mapping.py` regenerates the mapping document from
|
|
186
|
+
the rule module docstrings.
|
|
187
|
+
|
|
188
|
+
## Limitations
|
|
189
|
+
|
|
190
|
+
This phase does not parse HCL, resolve resources created outside Terraform,
|
|
191
|
+
inspect `null_resource`/local-exec or embedded CloudFormation, or provide
|
|
192
|
+
baseline support. Dynamic policy documents that cannot be statically resolved
|
|
193
|
+
are reported as UNKNOWN and follow `--unknown-as`. Terraform plan JSON does
|
|
194
|
+
not contain source file or line locations; SARIF locations therefore contain
|
|
195
|
+
Terraform resource addresses. The implementation follows the tf-nag AWS
|
|
196
|
+
Solutions IDs and levels, not Checkov/tfsec IDs.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Rule mapping
|
|
2
|
+
|
|
3
|
+
Generated from the vendored AWS Solutions inventory.
|
|
4
|
+
|
|
5
|
+
| Rule | Level | Status | CloudFormation origin | Terraform coverage |
|
|
6
|
+
|---|---|---|---|---|
|
|
7
|
+
| `AwsSolutions-AEC1` | ERROR | implemented | CacheCluster, ReplicationGroup | Implemented by tf-nag rules. |
|
|
8
|
+
| `AwsSolutions-AEC3` | ERROR | implemented | ReplicationGroup | Implemented by tf-nag rules. |
|
|
9
|
+
| `AwsSolutions-AEC4` | ERROR | implemented | ReplicationGroup | Implemented by tf-nag rules. |
|
|
10
|
+
| `AwsSolutions-AEC5` | ERROR | implemented | CacheCluster, ReplicationGroup | Implemented by tf-nag rules. |
|
|
11
|
+
| `AwsSolutions-AEC6` | ERROR | implemented | ReplicationGroup | Implemented by tf-nag rules. |
|
|
12
|
+
| `AwsSolutions-APIG1` | ERROR | implemented | Stage, V2Stage | Implemented by tf-nag rules. |
|
|
13
|
+
| `AwsSolutions-APIG2` | ERROR | implemented | RequestValidator, RestApi | Implemented by tf-nag rules. |
|
|
14
|
+
| `AwsSolutions-APIG3` | WARN | implemented | Stage, WebACLAssociation | Implemented by tf-nag rules. |
|
|
15
|
+
| `AwsSolutions-APIG4` | ERROR | implemented | Method, Route | Implemented by tf-nag rules. |
|
|
16
|
+
| `AwsSolutions-APIG6` | ERROR | implemented | Stage | Implemented by tf-nag rules. |
|
|
17
|
+
| `AwsSolutions-AS1` | ERROR | implemented | AutoScalingGroup | Implemented by tf-nag rules. |
|
|
18
|
+
| `AwsSolutions-AS2` | ERROR | implemented | AutoScalingGroup | Implemented by tf-nag rules. |
|
|
19
|
+
| `AwsSolutions-AS3` | ERROR | implemented | AutoScalingGroup | Implemented by tf-nag rules. |
|
|
20
|
+
| `AwsSolutions-ASC3` | ERROR | implemented | GraphQLApi | Implemented by tf-nag rules. |
|
|
21
|
+
| `AwsSolutions-C91` | ERROR | implemented | EnvironmentEC2 | Implemented by tf-nag rules. |
|
|
22
|
+
| `AwsSolutions-CB4` | ERROR | implemented | Project | Implemented by tf-nag rules. |
|
|
23
|
+
| `AwsSolutions-CB5` | WARN | implemented | Project | Implemented by tf-nag rules. |
|
|
24
|
+
| `AwsSolutions-CFR1` | WARN | implemented | Distribution | Implemented by tf-nag rules. |
|
|
25
|
+
| `AwsSolutions-CFR2` | WARN | implemented | Distribution | Implemented by tf-nag rules. |
|
|
26
|
+
| `AwsSolutions-CFR3` | ERROR | implemented | Distribution, StreamingDistribution | Implemented by tf-nag rules. |
|
|
27
|
+
| `AwsSolutions-CFR4` | ERROR | implemented | Distribution | Implemented by tf-nag rules. |
|
|
28
|
+
| `AwsSolutions-CFR5` | ERROR | implemented | Distribution | Implemented by tf-nag rules. |
|
|
29
|
+
| `AwsSolutions-CFR6` | ERROR | not-applicable-to-terraform | StreamingDistribution | Terraform AWS provider has no CloudFront streaming-distribution resource. |
|
|
30
|
+
| `AwsSolutions-CFR7` | ERROR | implemented | Bucket, Distribution | Implemented by tf-nag rules. |
|
|
31
|
+
| `AwsSolutions-COG1` | ERROR | implemented | UserPool | Implemented by tf-nag rules. |
|
|
32
|
+
| `AwsSolutions-COG2` | WARN | implemented | UserPool | Implemented by tf-nag rules. |
|
|
33
|
+
| `AwsSolutions-COG4` | ERROR | implemented | Method | Implemented by tf-nag rules. |
|
|
34
|
+
| `AwsSolutions-COG7` | ERROR | implemented | IdentityPool | Implemented by tf-nag rules. |
|
|
35
|
+
| `AwsSolutions-COG8` | ERROR | implemented | UserPool | Implemented by tf-nag rules. |
|
|
36
|
+
| `AwsSolutions-DDB3` | WARN | implemented | Table | Implemented by tf-nag rules. |
|
|
37
|
+
| `AwsSolutions-DDB4` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
38
|
+
| `AwsSolutions-DOC1` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
39
|
+
| `AwsSolutions-DOC2` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
40
|
+
| `AwsSolutions-DOC3` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
41
|
+
| `AwsSolutions-DOC4` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
42
|
+
| `AwsSolutions-DOC5` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
43
|
+
| `AwsSolutions-EB1` | ERROR | implemented | Environment | Implemented by tf-nag rules. |
|
|
44
|
+
| `AwsSolutions-EB3` | ERROR | implemented | Environment | Implemented by tf-nag rules. |
|
|
45
|
+
| `AwsSolutions-EB4` | WARN | implemented | Environment | Implemented by tf-nag rules. |
|
|
46
|
+
| `AwsSolutions-EC23` | ERROR | implemented | SecurityGroup, SecurityGroupIngress | Implemented by tf-nag rules. |
|
|
47
|
+
| `AwsSolutions-EC26` | ERROR | implemented | AutoScalingGroup, Instance, LaunchConfiguration, LaunchTemplate, Volume | Implemented by tf-nag rules. |
|
|
48
|
+
| `AwsSolutions-EC27` | ERROR | implemented | SecurityGroup | Implemented by tf-nag rules. |
|
|
49
|
+
| `AwsSolutions-EC28` | ERROR | implemented | Instance, LaunchConfiguration | Implemented by tf-nag rules. |
|
|
50
|
+
| `AwsSolutions-EC29` | ERROR | implemented | Instance | Implemented by tf-nag rules. |
|
|
51
|
+
| `AwsSolutions-ECR1` | ERROR | implemented | RegistryPolicy, Repository | Implemented by tf-nag rules. |
|
|
52
|
+
| `AwsSolutions-ECS2` | ERROR | implemented | TaskDefinition | Implemented by tf-nag rules. |
|
|
53
|
+
| `AwsSolutions-ECS4` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
54
|
+
| `AwsSolutions-ECS7` | ERROR | implemented | TaskDefinition | Implemented by tf-nag rules. |
|
|
55
|
+
| `AwsSolutions-EFS1` | ERROR | implemented | FileSystem | Implemented by tf-nag rules. |
|
|
56
|
+
| `AwsSolutions-EKS1` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
57
|
+
| `AwsSolutions-EKS2` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
58
|
+
| `AwsSolutions-ELB1` | ERROR | implemented | LoadBalancer | Implemented by tf-nag rules. |
|
|
59
|
+
| `AwsSolutions-ELB2` | ERROR | implemented | LoadBalancer, LoadBalancerV2 | Implemented by tf-nag rules. |
|
|
60
|
+
| `AwsSolutions-ELB3` | ERROR | implemented | LoadBalancer | Implemented by tf-nag rules. |
|
|
61
|
+
| `AwsSolutions-ELB4` | ERROR | implemented | LoadBalancer | Implemented by tf-nag rules. |
|
|
62
|
+
| `AwsSolutions-ELB5` | ERROR | implemented | LoadBalancer | Implemented by tf-nag rules. |
|
|
63
|
+
| `AwsSolutions-EMR2` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
64
|
+
| `AwsSolutions-EMR4` | ERROR | implemented | Cluster, SecurityConfiguration | Implemented by tf-nag rules. |
|
|
65
|
+
| `AwsSolutions-EMR5` | ERROR | implemented | Cluster, SecurityConfiguration | Implemented by tf-nag rules. |
|
|
66
|
+
| `AwsSolutions-EMR6` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
67
|
+
| `AwsSolutions-EVB1` | ERROR | implemented | EventBusPolicy | Implemented by tf-nag rules. |
|
|
68
|
+
| `AwsSolutions-GL1` | WARN | implemented | Crawler, Job, SecurityConfiguration | Implemented by tf-nag rules. |
|
|
69
|
+
| `AwsSolutions-GL3` | WARN | implemented | Job, SecurityConfiguration | Implemented by tf-nag rules. |
|
|
70
|
+
| `AwsSolutions-IAM4` | ERROR | implemented | Group, Role, User | Implemented by tf-nag rules. |
|
|
71
|
+
| `AwsSolutions-IAM5` | ERROR | implemented | Group, ManagedPolicy, Policy, Role, User | Implemented by tf-nag rules. |
|
|
72
|
+
| `AwsSolutions-KDA3` | WARN | implemented | ApplicationV2 | Implemented by tf-nag rules. |
|
|
73
|
+
| `AwsSolutions-KDF1` | ERROR | implemented | DeliveryStream | Implemented by tf-nag rules. |
|
|
74
|
+
| `AwsSolutions-KDS1` | ERROR | implemented | Stream | Implemented by tf-nag rules. |
|
|
75
|
+
| `AwsSolutions-KDS3` | WARN | implemented | Stream | Implemented by tf-nag rules. |
|
|
76
|
+
| `AwsSolutions-KMS5` | ERROR | implemented | Key | Implemented by tf-nag rules. |
|
|
77
|
+
| `AwsSolutions-L1` | ERROR | implemented | Function | Implemented by tf-nag rules. |
|
|
78
|
+
| `AwsSolutions-LEX4` | ERROR | implemented | Bot, BotAlias, LogGroup | Implemented by tf-nag rules. |
|
|
79
|
+
| `AwsSolutions-MS1` | ERROR | not-applicable-to-terraform | Container | Terraform AWS provider exposes no MediaStore access-logging configuration. |
|
|
80
|
+
| `AwsSolutions-MS10` | WARN | not-applicable-to-terraform | Container | Terraform AWS provider exposes no MediaStore lifecycle-policy resource. |
|
|
81
|
+
| `AwsSolutions-MS3` | ERROR | implemented | Container | Implemented by tf-nag rules. |
|
|
82
|
+
| `AwsSolutions-MS4` | WARN | not-applicable-to-terraform | Container | Terraform AWS provider exposes no MediaStore CloudWatch metric-policy resource. |
|
|
83
|
+
| `AwsSolutions-MS7` | WARN | implemented | Container | Implemented by tf-nag rules. |
|
|
84
|
+
| `AwsSolutions-MS8` | WARN | not-applicable-to-terraform | Container | Terraform AWS provider exposes no MediaStore CORS-policy resource. |
|
|
85
|
+
| `AwsSolutions-MSK2` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
86
|
+
| `AwsSolutions-MSK3` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
87
|
+
| `AwsSolutions-MSK6` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
88
|
+
| `AwsSolutions-N1` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
89
|
+
| `AwsSolutions-N2` | ERROR | implemented | DBInstance | Implemented by tf-nag rules. |
|
|
90
|
+
| `AwsSolutions-N3` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
91
|
+
| `AwsSolutions-N4` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
92
|
+
| `AwsSolutions-N5` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
93
|
+
| `AwsSolutions-OS1` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
94
|
+
| `AwsSolutions-OS2` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
95
|
+
| `AwsSolutions-OS3` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
96
|
+
| `AwsSolutions-OS4` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
97
|
+
| `AwsSolutions-OS5` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
98
|
+
| `AwsSolutions-OS7` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
99
|
+
| `AwsSolutions-OS8` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
100
|
+
| `AwsSolutions-OS9` | ERROR | implemented | Domain | Implemented by tf-nag rules. |
|
|
101
|
+
| `AwsSolutions-QS1` | ERROR | implemented | DataSource | Implemented by tf-nag rules. |
|
|
102
|
+
| `AwsSolutions-RDS10` | ERROR | implemented | DBCluster, DBInstance | Implemented by tf-nag rules. |
|
|
103
|
+
| `AwsSolutions-RDS11` | ERROR | implemented | DBCluster, DBInstance | Implemented by tf-nag rules. |
|
|
104
|
+
| `AwsSolutions-RDS13` | ERROR | implemented | DBInstance | Implemented by tf-nag rules. |
|
|
105
|
+
| `AwsSolutions-RDS14` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
106
|
+
| `AwsSolutions-RDS16` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
107
|
+
| `AwsSolutions-RDS2` | ERROR | implemented | DBCluster, DBInstance | Implemented by tf-nag rules. |
|
|
108
|
+
| `AwsSolutions-RDS3` | ERROR | implemented | DBInstance | Implemented by tf-nag rules. |
|
|
109
|
+
| `AwsSolutions-RDS6` | ERROR | implemented | DBCluster | Implemented by tf-nag rules. |
|
|
110
|
+
| `AwsSolutions-RDS8` | ERROR | implemented | DBSecurityGroup, DBSecurityGroupIngress | Implemented by tf-nag rules. |
|
|
111
|
+
| `AwsSolutions-RS1` | ERROR | implemented | Cluster, ClusterParameterGroup | Implemented by tf-nag rules. |
|
|
112
|
+
| `AwsSolutions-RS10` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
113
|
+
| `AwsSolutions-RS11` | ERROR | implemented | Cluster, ClusterParameterGroup | Implemented by tf-nag rules. |
|
|
114
|
+
| `AwsSolutions-RS2` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
115
|
+
| `AwsSolutions-RS3` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
116
|
+
| `AwsSolutions-RS4` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
117
|
+
| `AwsSolutions-RS5` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
118
|
+
| `AwsSolutions-RS6` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
119
|
+
| `AwsSolutions-RS8` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
120
|
+
| `AwsSolutions-RS9` | ERROR | implemented | Cluster | Implemented by tf-nag rules. |
|
|
121
|
+
| `AwsSolutions-S1` | ERROR | implemented | Bucket | Implemented by tf-nag rules. |
|
|
122
|
+
| `AwsSolutions-S10` | ERROR | implemented | Bucket, BucketPolicy | Implemented by tf-nag rules. |
|
|
123
|
+
| `AwsSolutions-S2` | ERROR | implemented | Bucket | Implemented by tf-nag rules. |
|
|
124
|
+
| `AwsSolutions-S5` | ERROR | implemented | Bucket, BucketPolicy | Implemented by tf-nag rules. |
|
|
125
|
+
| `AwsSolutions-SF1` | ERROR | implemented | StateMachine | Implemented by tf-nag rules. |
|
|
126
|
+
| `AwsSolutions-SF2` | ERROR | implemented | StateMachine | Implemented by tf-nag rules. |
|
|
127
|
+
| `AwsSolutions-SM1` | ERROR | implemented | NotebookInstance | Implemented by tf-nag rules. |
|
|
128
|
+
| `AwsSolutions-SM2` | ERROR | implemented | NotebookInstance | Implemented by tf-nag rules. |
|
|
129
|
+
| `AwsSolutions-SM3` | ERROR | implemented | NotebookInstance | Implemented by tf-nag rules. |
|
|
130
|
+
| `AwsSolutions-SMG4` | ERROR | implemented | RotationSchedule, Secret, SecretTargetAttachment, TargetAttachment | Implemented by tf-nag rules. |
|
|
131
|
+
| `AwsSolutions-SNS3` | ERROR | implemented | Topic, TopicPolicy | Implemented by tf-nag rules. |
|
|
132
|
+
| `AwsSolutions-SQS2` | ERROR | implemented | Queue | Implemented by tf-nag rules. |
|
|
133
|
+
| `AwsSolutions-SQS3` | ERROR | implemented | Function, Queue | Implemented by tf-nag rules. |
|
|
134
|
+
| `AwsSolutions-SQS4` | ERROR | implemented | Queue, QueuePolicy | Implemented by tf-nag rules. |
|
|
135
|
+
| `AwsSolutions-TS3` | WARN | implemented | Database | Implemented by tf-nag rules. |
|
|
136
|
+
| `AwsSolutions-VPC3` | WARN | implemented | NetworkAcl, NetworkAclEntry | Implemented by tf-nag rules. |
|
|
137
|
+
| `AwsSolutions-VPC7` | ERROR | implemented | FlowLog, VPC | Implemented by tf-nag rules. |
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tf-nag"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Offline Terraform plan scanner for the tf-nag AWS Solutions rules"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Repository = "https://github.com/vumdao/terraform-nag.git"
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
tf-nag = "tfnag.cli:main"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["tfnag"]
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8",
|
|
24
|
+
"ruff>=0.6",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
line-length = 100
|
|
29
|
+
target-version = "py311"
|
|
30
|
+
|
|
31
|
+
[tool.ruff.lint]
|
|
32
|
+
select = ["E", "F", "I"]
|
|
33
|
+
ignore = ["E501"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Regenerate docs/rule-mapping.md from the vendored rule inventory."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from tfnag.registry import INVENTORY, discover, status
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
discover()
|
|
10
|
+
lines = [
|
|
11
|
+
"# Rule mapping",
|
|
12
|
+
"",
|
|
13
|
+
"Generated from the vendored AWS Solutions inventory.",
|
|
14
|
+
"",
|
|
15
|
+
"| Rule | Level | Status | CloudFormation origin | Terraform coverage |",
|
|
16
|
+
"|---|---|---|---|---|",
|
|
17
|
+
]
|
|
18
|
+
for rule_id, item in sorted(INVENTORY.items()):
|
|
19
|
+
origin = ", ".join(item.get("cfn", []))
|
|
20
|
+
coverage = item.get("not_applicable_reason", "Implemented by tf-nag rules.")
|
|
21
|
+
coverage = coverage.replace("|", "\\|")
|
|
22
|
+
lines.append(
|
|
23
|
+
f"| `{rule_id}` | {item['level']} | {status(rule_id)} | {origin} | {coverage} |"
|
|
24
|
+
)
|
|
25
|
+
Path("docs/rule-mapping.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|